Remove unused files and code
This commit is contained in:
parent
71a244dd30
commit
9a8d5ca038
@ -1,3 +0,0 @@
|
||||
module.exports = {
|
||||
presets: ['@babel/preset-env', '@babel/preset-react'],
|
||||
};
|
2
gui
2
gui
@ -1 +1 @@
|
||||
Subproject commit 46aca6809f49bf51f92ab4fcf27f614e5edc8207
|
||||
Subproject commit a660eee25f73c81d0f9353462655853cbf5abc3a
|
24
package.json
24
package.json
@ -825,8 +825,6 @@
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/core": "^7.21.8",
|
||||
"@babel/preset-env": "^7.21.5",
|
||||
"@babel/preset-react": "^7.18.6",
|
||||
"@babel/preset-typescript": "^7.21.5",
|
||||
"@types/chai": "^4.3.5",
|
||||
"@types/glob": "^8.1.0",
|
||||
@ -835,8 +833,6 @@
|
||||
"@types/ncp": "^2.0.5",
|
||||
"@types/node": "16.x",
|
||||
"@types/proxyquire": "^1.3.28",
|
||||
"@types/react-dom": "^18.2.3",
|
||||
"@types/react-syntax-highlighter": "^15.5.6",
|
||||
"@types/shell-escape": "^0.2.1",
|
||||
"@types/sinon": "^10.0.15",
|
||||
"@types/uuid": "^9.0.1",
|
||||
@ -847,7 +843,6 @@
|
||||
"babel-loader": "^9.1.2",
|
||||
"chai": "^4.3.7",
|
||||
"copy-webpack-plugin": "^11.0.0",
|
||||
"css-loader": "^6.7.3",
|
||||
"dotenv": "^16.0.3",
|
||||
"eslint": "^8.36.0",
|
||||
"file-loader": "^6.2.0",
|
||||
@ -858,31 +853,19 @@
|
||||
"mocha": "^10.2.0",
|
||||
"mock-fs": "^5.2.0",
|
||||
"proxyquire": "^2.1.3",
|
||||
"react": "^18.2.0",
|
||||
"react-dom": "^18.2.0",
|
||||
"sinon": "^15.1.0",
|
||||
"style-loader": "^3.3.2",
|
||||
"ts-jest": "^29.1.0",
|
||||
"ts-loader": "^9.4.2",
|
||||
"ts-node": "^10.9.1",
|
||||
"typescript": "^4.9.5",
|
||||
"url-loader": "^4.1.1",
|
||||
"vscode-test": "^1.6.1",
|
||||
"webpack": "^5.76.3",
|
||||
"webpack-cli": "^5.0.1",
|
||||
"webpack-dev-server": "^4.13.3"
|
||||
},
|
||||
"dependencies": {
|
||||
"@emotion/react": "^11.10.8",
|
||||
"@mantine/core": "^6.0.10",
|
||||
"@mantine/dropzone": "^6.0.10",
|
||||
"@mantine/hooks": "^6.0.10",
|
||||
"@mantine/prism": "^6.0.10",
|
||||
"@mantine/tiptap": "^6.0.10",
|
||||
"@tabler/icons-react": "^2.17.0",
|
||||
"@tiptap/extension-link": "^2.0.3",
|
||||
"@tiptap/pm": "^2.0.0",
|
||||
"@tiptap/react": "^2.0.3",
|
||||
"@tiptap/starter-kit": "^2.0.3",
|
||||
"axios": "^1.3.6",
|
||||
"clean-webpack-plugin": "^4.0.0",
|
||||
@ -890,17 +873,10 @@
|
||||
"dotenv": "^16.0.3",
|
||||
"js-yaml": "^4.1.0",
|
||||
"mdast": "^3.0.0",
|
||||
"mdast-util-from-markdown": "^2.0.0",
|
||||
"mdast-util-to-markdown": "^2.1.0",
|
||||
"mobx": "^6.10.0",
|
||||
"mobx-react": "^9.0.0",
|
||||
"mobx-state-tree": "^5.1.8",
|
||||
"ncp": "^2.0.0",
|
||||
"node-fetch": "^3.3.1",
|
||||
"nonce": "^1.0.4",
|
||||
"quote": "^0.4.0",
|
||||
"react-markdown": "^8.0.7",
|
||||
"react-syntax-highlighter": "^15.5.0",
|
||||
"rehype-raw": "^6.1.1",
|
||||
"shell-escape": "^0.2.0",
|
||||
"string-argv": "^0.3.2",
|
||||
|
@ -1,21 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>DevChat</title>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<style>
|
||||
body {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div id="app"></div>
|
||||
<script src="<vscode-resource:/index.js>"></script>
|
||||
</body>
|
||||
|
||||
</html>
|
@ -1,45 +0,0 @@
|
||||
import * as React from 'react';
|
||||
import { createRoot } from 'react-dom/client';
|
||||
import { MantineProvider, MantineThemeOverride } from '@mantine/core';
|
||||
import { Provider, rootStore } from '@/views/stores/RootStore';
|
||||
import App from '@/views/App';
|
||||
|
||||
const container = document.getElementById('app')!;
|
||||
const root = createRoot(container); // createRoot(container!) if you use TypeScript
|
||||
const myTheme: MantineThemeOverride = {
|
||||
fontFamily: 'var(--vscode-editor-font-family)',
|
||||
colors: {
|
||||
"merico":[
|
||||
"#F9F5F4",
|
||||
"#EADAD6",
|
||||
"#E1C0B6",
|
||||
"#DEA594",
|
||||
"#E1886F",
|
||||
"#ED6A45",
|
||||
"#D75E3C",
|
||||
"#BD573B",
|
||||
"#9F5541",
|
||||
"#865143",
|
||||
],
|
||||
},
|
||||
primaryColor: 'merico',
|
||||
components: {
|
||||
Text: {
|
||||
styles: {
|
||||
root:{
|
||||
fontSize: 'var(--vscode-editor-font-size)',
|
||||
}
|
||||
}
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
|
||||
root.render(
|
||||
<MantineProvider withGlobalStyles withNormalizeCSS withCSSVariables
|
||||
theme={myTheme}>
|
||||
<Provider value={rootStore}>
|
||||
<App />
|
||||
</Provider>
|
||||
</MantineProvider>
|
||||
);
|
@ -1,19 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<title>DevChat</title>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<style>
|
||||
body {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div id="app"></div>
|
||||
<script></script>
|
||||
</body>
|
||||
</html>
|
@ -1,77 +0,0 @@
|
||||
import IdeaBridge from "./ideaBridge";
|
||||
|
||||
class MessageUtil {
|
||||
private static instance: MessageUtil;
|
||||
|
||||
handlers: { [x: string]: any };
|
||||
vscodeApi: any;
|
||||
messageListener: any;
|
||||
|
||||
constructor() {
|
||||
this.handlers = {};
|
||||
this.messageListener = null;
|
||||
if (process.env.platform === "vscode") {
|
||||
this.vscodeApi = window.acquireVsCodeApi();
|
||||
}
|
||||
|
||||
if (!this.messageListener) {
|
||||
this.messageListener = (event: { data: any }) => {
|
||||
const message = event.data;
|
||||
this.handleMessage(message);
|
||||
};
|
||||
window.addEventListener("message", this.messageListener);
|
||||
} else {
|
||||
console.log("Message listener has already been bound.");
|
||||
}
|
||||
}
|
||||
|
||||
public static getInstance(): MessageUtil {
|
||||
if (!MessageUtil.instance) {
|
||||
MessageUtil.instance = new MessageUtil();
|
||||
}
|
||||
return MessageUtil.instance;
|
||||
}
|
||||
|
||||
// Register a message handler for a specific message type
|
||||
registerHandler(messageType: string, handler: any) {
|
||||
if (process.env.platform === "idea") {
|
||||
IdeaBridge.registerHandler(messageType, handler);
|
||||
} else {
|
||||
if (!this.handlers[messageType]) {
|
||||
this.handlers[messageType] = [];
|
||||
}
|
||||
this.handlers[messageType].push(handler);
|
||||
}
|
||||
}
|
||||
|
||||
// Unregister a message handler for a specific message type
|
||||
unregisterHandler(messageType: string | number, handler: any) {
|
||||
if (this.handlers[messageType]) {
|
||||
this.handlers[messageType] = this.handlers[messageType].filter(
|
||||
(h: any) => h !== handler
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// Handle a received message
|
||||
handleMessage(message: { command: string | number }) {
|
||||
const handlers = this.handlers[message.command];
|
||||
if (handlers) {
|
||||
handlers.forEach((handler: (arg0: { command: string | number }) => any) =>
|
||||
handler(message)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// Send a message to the VSCode API
|
||||
sendMessage(message: any) {
|
||||
if (process.env.platform === "idea") {
|
||||
IdeaBridge.sendMessage(message);
|
||||
} else {
|
||||
this.vscodeApi.postMessage(message);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Export the MessageUtil class as a module
|
||||
export default MessageUtil.getInstance();
|
@ -1,70 +0,0 @@
|
||||
## sendMessage
|
||||
|
||||
- getUserAccessKey // 获取 access key
|
||||
- doCommit // 提交代码
|
||||
- updateSetting // 更新设置(目前只有更换模型)
|
||||
- getSetting // 获取默认模型
|
||||
- deleteChatMessage // 删除最近一条消息
|
||||
- show_diff // 调用 editor 代码对比
|
||||
-- later --
|
||||
- stopDevChat // 停止生成
|
||||
- doCommand //
|
||||
-- content
|
||||
// 1. 打开设置
|
||||
// 2. 启动 ask code 安装
|
||||
// 3. 设置 access key
|
||||
- featureToggles ??
|
||||
- isDevChatInstalled // 判断 ask code 是否安装
|
||||
|
||||
- historyMessages // 页面的历史消息
|
||||
- contextDetail // 获取 appendContext 响应之后,发送次请求获取文件的内容
|
||||
- addContext // 点击 context 菜单(比如 git diff)之后发送到消息
|
||||
- code_file_apply // 代码应用到 editor,替换 current file
|
||||
- code_apply // 代码应用到 editor 光标位置
|
||||
- sendMessage // 发送消息
|
||||
- regeneration // 错误时重新生成
|
||||
- regContextList // git diff 之类的列表
|
||||
- regModelList // model 列表
|
||||
- regCommandList // 输入 / 之后出现的列表
|
||||
|
||||
## registerHandler
|
||||
|
||||
- getUserAccessKey // 获取 access key
|
||||
- regCommandList // 获取 / 之后出现的列表
|
||||
- appendContext // 右键添加到 context 或者 context 菜单点击的响应
|
||||
- contextDetailResponse // 获取到的文件内容
|
||||
- loadHistoryMessages // 与 historyMessages 对应
|
||||
- isDevChatInstalled // 与 isDevChatInstalled 对应
|
||||
- deletedChatMessage // 与 deleteChatMessage 对应
|
||||
- regContextList // 与 regContextList 对应
|
||||
- regModelList // 与 regModelList
|
||||
- receiveMessagePartial // 部分对话
|
||||
- receiveMessage // 对话
|
||||
- systemMessage // 没用了
|
||||
|
||||
# css
|
||||
|
||||
--vscode-editor-font-familyy
|
||||
--vscode-editor-font-size
|
||||
--vscode-dropdown-background
|
||||
--vscode-dropdown-border
|
||||
--vscode-foreground
|
||||
--vscode-sideBar-background
|
||||
--vscode-dropdown-foreground
|
||||
--vscode-menu-foreground
|
||||
--vscode-commandCenter-activeForeground
|
||||
--vscode-commandCenter-activeBackground
|
||||
--vscode-menu-border
|
||||
--vscode-menu-background
|
||||
--vscode-commandCenter-border
|
||||
--vscode-editor-foreground
|
||||
--vscode-input-background
|
||||
--vscode-input-border
|
||||
--vscode-input-foreground
|
||||
--vscode-disabledForeground
|
||||
--vscode-toolbar-activeBackground
|
||||
|
||||
1. 请求 loadHistoryMessages 根据全局 id 返回空/对应的消息
|
||||
1.1. 参数只有分页
|
||||
2. 请求 loadConversations,用于切换全局的 id
|
||||
2.1 loadConversations 的响应里,请求 1
|
@ -1,487 +0,0 @@
|
||||
const JStoIdea = {
|
||||
sendMessage: (message: string, context: any = [], parent: string = "") => {
|
||||
const paramsContext: any = [];
|
||||
if (Array.isArray(context) && context.length > 0) {
|
||||
context.forEach((item) => {
|
||||
paramsContext.push({
|
||||
type: "code",
|
||||
...item.context,
|
||||
});
|
||||
});
|
||||
}
|
||||
const params = {
|
||||
action: "sendMessage/request",
|
||||
metadata: {
|
||||
callback: "IdeaToJSMessage",
|
||||
parent: parent,
|
||||
},
|
||||
payload: {
|
||||
contexts: paramsContext,
|
||||
message: message,
|
||||
},
|
||||
};
|
||||
console.log("ready to send message: ", params);
|
||||
window.JSJavaBridge.callJava(JSON.stringify(params));
|
||||
},
|
||||
getModel: () => {
|
||||
const params = {
|
||||
action: "listModels/request",
|
||||
metadata: {
|
||||
callback: "IdeaToJSMessage",
|
||||
},
|
||||
payload: {},
|
||||
};
|
||||
window.JSJavaBridge.callJava(JSON.stringify(params));
|
||||
},
|
||||
getContextList: () => {
|
||||
const params = {
|
||||
action: "listContexts/request",
|
||||
metadata: {
|
||||
callback: "IdeaToJSMessage",
|
||||
},
|
||||
payload: {},
|
||||
};
|
||||
|
||||
window.JSJavaBridge.callJava(JSON.stringify(params));
|
||||
},
|
||||
getCommandList: () => {
|
||||
const params = {
|
||||
action: "listCommands/request",
|
||||
metadata: {
|
||||
callback: "IdeaToJSMessage",
|
||||
},
|
||||
payload: {},
|
||||
};
|
||||
|
||||
window.JSJavaBridge.callJava(JSON.stringify(params));
|
||||
},
|
||||
insertCode: (code) => {
|
||||
const params = {
|
||||
action: "insertCode/request",
|
||||
metadata: {
|
||||
callback: "IdeaToJSMessage",
|
||||
},
|
||||
payload: {
|
||||
content: code,
|
||||
},
|
||||
};
|
||||
|
||||
window.JSJavaBridge.callJava(JSON.stringify(params));
|
||||
},
|
||||
replaceFileContent: (code) => {
|
||||
const params = {
|
||||
action: "replaceFileContent/request",
|
||||
metadata: {
|
||||
callback: "IdeaToJSMessage",
|
||||
},
|
||||
payload: {
|
||||
content: code,
|
||||
},
|
||||
};
|
||||
|
||||
window.JSJavaBridge.callJava(JSON.stringify(params));
|
||||
},
|
||||
viewDiff: (code) => {
|
||||
const params = {
|
||||
action: "viewDiff/request",
|
||||
metadata: {
|
||||
callback: "IdeaToJSMessage",
|
||||
},
|
||||
payload: {
|
||||
content: code,
|
||||
},
|
||||
};
|
||||
|
||||
window.JSJavaBridge.callJava(JSON.stringify(params));
|
||||
},
|
||||
getUserAccessKey: () => {
|
||||
// 这里需要发送一个请求,获取完整的用户设置
|
||||
const params = {
|
||||
action: "getSetting/request",
|
||||
metadata: {
|
||||
callback: "IdeaToJSMessage",
|
||||
},
|
||||
payload: {},
|
||||
};
|
||||
window.JSJavaBridge.callJava(JSON.stringify(params));
|
||||
},
|
||||
etcCommand: (command: any) => {
|
||||
/**
|
||||
* 有四种命令
|
||||
* 1. workbench.action.openSettings
|
||||
* 2. AskCodeIndexStart
|
||||
* 3. AccessKey.OpenAI
|
||||
* 4. AccessKey.DevChat
|
||||
*/
|
||||
const content = Array.isArray(command.content) ? command.content[0] : "";
|
||||
switch (content) {
|
||||
case "workbench.action.openSettings":
|
||||
// 打开设置
|
||||
const params = {
|
||||
action: "showSettingDialog/request",
|
||||
metadata: {
|
||||
callback: "IdeaToJSMessage",
|
||||
},
|
||||
payload: {},
|
||||
};
|
||||
|
||||
window.JSJavaBridge.callJava(JSON.stringify(params));
|
||||
break;
|
||||
case "AccessKey.DevChat":
|
||||
// 设置key
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
},
|
||||
getTopicList: () => {
|
||||
// 获取 topic 列表
|
||||
const params = {
|
||||
action: "listTopics/request",
|
||||
metadata: {
|
||||
callback: "IdeaToJSMessage",
|
||||
},
|
||||
payload: {},
|
||||
};
|
||||
|
||||
window.JSJavaBridge.callJava(JSON.stringify(params));
|
||||
},
|
||||
updateSetting: (value: string) => {
|
||||
// 因为现在只有更换模型,所以直接取 value
|
||||
const params = {
|
||||
action: "updateSetting/request",
|
||||
metadata: {
|
||||
callback: "IdeaToJSMessage",
|
||||
},
|
||||
payload: {
|
||||
setting: {
|
||||
currentModel: value,
|
||||
},
|
||||
},
|
||||
};
|
||||
window.JSJavaBridge.callJava(JSON.stringify(params));
|
||||
},
|
||||
commit: (code: string) => {
|
||||
const params = {
|
||||
action: "commitCode/request",
|
||||
metadata: {
|
||||
callback: "IdeaToJSMessage",
|
||||
},
|
||||
payload: {
|
||||
message: code,
|
||||
},
|
||||
};
|
||||
window.JSJavaBridge.callJava(JSON.stringify(params));
|
||||
},
|
||||
getTopicDetail: (topicHash: string) => {
|
||||
const params = {
|
||||
action: "loadConversations/request",
|
||||
metadata: {
|
||||
callback: "IdeaToJSMessage",
|
||||
topicHash: topicHash,
|
||||
},
|
||||
payload: {},
|
||||
};
|
||||
|
||||
window.JSJavaBridge.callJava(JSON.stringify(params));
|
||||
},
|
||||
historyMessages: (message) => {
|
||||
const params = {
|
||||
action: "loadHistoryMessages/request",
|
||||
metadata: {
|
||||
callback: "IdeaToJSMessage",
|
||||
},
|
||||
payload: {
|
||||
pageIndex: message?.page || 0,
|
||||
},
|
||||
};
|
||||
|
||||
window.JSJavaBridge.callJava(JSON.stringify(params));
|
||||
},
|
||||
deleteChatMessage: (message) => {
|
||||
const params = {
|
||||
action: "deleteLastConversation/request",
|
||||
metadata: {
|
||||
callback: "IdeaToJSMessage",
|
||||
},
|
||||
payload: {
|
||||
promptHash: message?.hash || "",
|
||||
},
|
||||
};
|
||||
|
||||
window.JSJavaBridge.callJava(JSON.stringify(params));
|
||||
},
|
||||
openLink: (message) => {
|
||||
if (!message?.url) {
|
||||
return false;
|
||||
}
|
||||
const params = {
|
||||
action: "openLink/request",
|
||||
metadata: {
|
||||
callback: "IdeaToJSMessage",
|
||||
},
|
||||
payload: {
|
||||
url: message?.url || "",
|
||||
},
|
||||
};
|
||||
|
||||
window.JSJavaBridge.callJava(JSON.stringify(params));
|
||||
},
|
||||
};
|
||||
|
||||
class IdeaBridge {
|
||||
private static instance: IdeaBridge;
|
||||
handle: any = {};
|
||||
|
||||
constructor() {
|
||||
this.handle = {};
|
||||
// 注册全局的回调函数,用于接收来自IDEA的消息
|
||||
window.IdeaToJSMessage = (res: any) => {
|
||||
console.log("IdeaToJSMessage: ", res);
|
||||
switch (res.action) {
|
||||
case "deleteLastConversation/response":
|
||||
this.resviceDeleteMessage(res);
|
||||
break;
|
||||
case "loadHistoryMessages/response":
|
||||
this.resviceHistoryMessages(res);
|
||||
break;
|
||||
case "sendMessage/response":
|
||||
this.resviceMessage(res);
|
||||
break;
|
||||
case "listModels/response":
|
||||
this.resviceModelList(res);
|
||||
break;
|
||||
case "listContexts/response":
|
||||
this.resviceContextList(res);
|
||||
break;
|
||||
case "listCommands/response":
|
||||
this.resviceCommandList(res);
|
||||
break;
|
||||
// 这里暂时不用,因为获取到的只有 key,信息不全
|
||||
// 所以用 resviceSettings 来获取
|
||||
// case "getKey/response":
|
||||
// this.resviceAccessKey(res.payload.key);
|
||||
// break;
|
||||
case "addContext/notify":
|
||||
this.resviesContext(res);
|
||||
break;
|
||||
case "getSetting/response":
|
||||
this.resviceSettings(res);
|
||||
break;
|
||||
case "listTopics/response":
|
||||
this.resviceTopicList(res);
|
||||
break;
|
||||
case "loadConversations/response":
|
||||
this.resviceTopicDetail(res);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
resviceDeleteMessage(res) {
|
||||
const hash = res?.payload?.promptHash || "";
|
||||
this.handle.deletedChatMessage({
|
||||
hash,
|
||||
});
|
||||
}
|
||||
|
||||
resviceHistoryMessages(res) {
|
||||
const list: any = [];
|
||||
if (res?.payload?.messages?.length > 0) {
|
||||
res?.payload?.messages.forEach((item) => {
|
||||
list.push({
|
||||
...item,
|
||||
response: item.responses?.join("\n"),
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
this.handle.reloadMessage({
|
||||
entries: list.reverse(),
|
||||
pageIndex: 0,
|
||||
});
|
||||
}
|
||||
|
||||
resviceTopicDetail(res) {
|
||||
// 用于重置后端全局的 topic id
|
||||
if (res?.payload?.reset) {
|
||||
// 重置后请求历史消息
|
||||
JStoIdea.historyMessages({ page: 0 });
|
||||
}
|
||||
}
|
||||
|
||||
resviceTopicList(res) {
|
||||
const list = res.payload.topics;
|
||||
this.handle.listTopics(list);
|
||||
}
|
||||
|
||||
resviesContext(res) {
|
||||
const params = {
|
||||
file: res.payload.path,
|
||||
result: "",
|
||||
};
|
||||
const contextObj = {
|
||||
path: res.payload.path,
|
||||
content: res.payload.content,
|
||||
command: "",
|
||||
};
|
||||
params.result = JSON.stringify(contextObj);
|
||||
this.handle.contextDetailResponse(params);
|
||||
}
|
||||
|
||||
resviceSettings(res) {
|
||||
// 用户设置的回调
|
||||
const setting = res.payload.setting;
|
||||
|
||||
// 当前的默认模型
|
||||
this.handle.getSetting({
|
||||
value: setting.currentModel,
|
||||
});
|
||||
this.handle.getUserAccessKey({
|
||||
endPoint: setting.apiBase,
|
||||
accessKey: setting.apiKey,
|
||||
keyType: setting.apiKey.startsWith("DC") ? "DevChat" : "OpenAi",
|
||||
});
|
||||
}
|
||||
|
||||
resviceAccessKey(res: string = "") {
|
||||
const params = {
|
||||
endPoint: "",
|
||||
accessKey: res,
|
||||
keyType: res.startsWith("DC") ? "DevChat" : "OpenAi",
|
||||
};
|
||||
this.handle.getUserAccessKey(params);
|
||||
}
|
||||
|
||||
resviceCommandList(res) {
|
||||
const result = res.payload.commands.map((item) => ({
|
||||
name: item.name,
|
||||
pattern: item.name,
|
||||
description: item.description,
|
||||
}));
|
||||
this.handle.regCommandList({
|
||||
result,
|
||||
});
|
||||
}
|
||||
|
||||
resviceContextList(res) {
|
||||
// 接受到的上下文列表
|
||||
|
||||
const result = res.payload.contexts.map((item) => ({
|
||||
name: item.command,
|
||||
pattern: item.command,
|
||||
description: item.description,
|
||||
}));
|
||||
|
||||
this.handle.regContextList({ result });
|
||||
}
|
||||
|
||||
resviceModelList(response: any) {
|
||||
// 接受到模型列表
|
||||
this.handle["regModelList"]({
|
||||
result: response.payload.models,
|
||||
});
|
||||
}
|
||||
|
||||
resviceMessage(response: any) {
|
||||
console.log(
|
||||
"response.metadata.isFinalChunk: ",
|
||||
response.metadata.isFinalChunk
|
||||
);
|
||||
// 接受到消息
|
||||
if (response.metadata?.isFinalChunk) {
|
||||
// 结束对话
|
||||
this.handle["receiveMessage"]({
|
||||
text: response.payload?.message || response.metadata?.error || "",
|
||||
isError: response.metadata?.error.length > 0,
|
||||
hash: response.payload?.promptHash || "",
|
||||
});
|
||||
} else {
|
||||
this.handle["receiveMessagePartial"]({
|
||||
text: response?.payload?.message || "",
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
public static getInstance(): IdeaBridge {
|
||||
if (!IdeaBridge.instance) {
|
||||
IdeaBridge.instance = new IdeaBridge();
|
||||
}
|
||||
return IdeaBridge.instance;
|
||||
}
|
||||
|
||||
registerHandler(messageType: string, handler: any) {
|
||||
// 注册回调函数
|
||||
this.handle[messageType] = handler;
|
||||
}
|
||||
|
||||
sendMessage(message: any) {
|
||||
// 根据 command 分发到不同的方法·
|
||||
switch (message.command) {
|
||||
// 发送消息
|
||||
case "sendMessage":
|
||||
JStoIdea.sendMessage(
|
||||
message.text,
|
||||
message.contextInfo,
|
||||
message.parent_hash
|
||||
);
|
||||
break;
|
||||
// 重新生成消息,用于发送失败时再次发送
|
||||
case "regeneration":
|
||||
JStoIdea.sendMessage(message.text, message.context, message.parent);
|
||||
break;
|
||||
// 请求 model 列表
|
||||
case "regModelList":
|
||||
JStoIdea.getModel();
|
||||
break;
|
||||
case "regContextList":
|
||||
JStoIdea.getContextList();
|
||||
break;
|
||||
case "regCommandList":
|
||||
JStoIdea.getCommandList();
|
||||
break;
|
||||
case "code_apply":
|
||||
JStoIdea.insertCode(message.content);
|
||||
break;
|
||||
case "code_file_apply":
|
||||
JStoIdea.replaceFileContent(message.content);
|
||||
break;
|
||||
case "getUserAccessKey":
|
||||
JStoIdea.getUserAccessKey();
|
||||
break;
|
||||
case "doCommand":
|
||||
JStoIdea.etcCommand(message);
|
||||
break;
|
||||
case "show_diff":
|
||||
JStoIdea.viewDiff(message.content);
|
||||
break;
|
||||
case "updateSetting":
|
||||
JStoIdea.updateSetting(message.value);
|
||||
break;
|
||||
case "doCommit":
|
||||
JStoIdea.commit(message.content);
|
||||
break;
|
||||
case "listTopics":
|
||||
JStoIdea.getTopicList();
|
||||
break;
|
||||
case "getTopicDetail":
|
||||
JStoIdea.getTopicDetail(message.topicHash);
|
||||
break;
|
||||
case "historyMessages":
|
||||
JStoIdea.historyMessages(message);
|
||||
break;
|
||||
case "deleteChatMessage":
|
||||
JStoIdea.deleteChatMessage(message);
|
||||
break;
|
||||
case "openLink":
|
||||
JStoIdea.openLink(message);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export default IdeaBridge.getInstance();
|
@ -1,12 +0,0 @@
|
||||
::-webkit-scrollbar {
|
||||
background-color: transparent;
|
||||
width: 6px;
|
||||
height: 6px;
|
||||
}
|
||||
::-webkit-scrollbar-track {
|
||||
background-color: transparent;
|
||||
}
|
||||
::-webkit-scrollbar-thumb {
|
||||
background-color: grey;
|
||||
border-radius: 6px;
|
||||
}
|
@ -1,26 +0,0 @@
|
||||
import * as React from 'react';
|
||||
import {
|
||||
AppShell,
|
||||
useMantineTheme,
|
||||
} from '@mantine/core';
|
||||
import ChatPanel from '@/views/pages/ChatPanel';
|
||||
import Head from '@/views/components/Header';
|
||||
import './App.css';
|
||||
|
||||
export default function App() {
|
||||
const theme = useMantineTheme();
|
||||
return (
|
||||
<AppShell
|
||||
header={<Head />}
|
||||
styles={{
|
||||
main: {
|
||||
padding:'40px 0 0 0',
|
||||
fontFamily: 'var(--vscode-editor-font-family)',
|
||||
fontSize: 'var(--vscode-editor-font-size)',
|
||||
},
|
||||
}}
|
||||
>
|
||||
<ChatPanel />
|
||||
</AppShell>
|
||||
);
|
||||
}
|
@ -1,159 +0,0 @@
|
||||
import React, { useEffect, useState } from "react";
|
||||
import axios from "axios";
|
||||
import messageUtil from "@/util/MessageUtil";
|
||||
import { IconWallet } from "@tabler/icons-react";
|
||||
import {
|
||||
HoverCard,
|
||||
Text,
|
||||
ActionIcon,
|
||||
Group,
|
||||
LoadingOverlay,
|
||||
} from "@mantine/core";
|
||||
|
||||
const currencyMap = {
|
||||
USD: "$",
|
||||
RMB: "¥",
|
||||
};
|
||||
|
||||
function formatBalance(balance: number) {
|
||||
return Math.round(balance * 1000) / 1000;
|
||||
}
|
||||
|
||||
function formatCurrency(balance: number, currency: string) {
|
||||
return `${currencyMap[currency] || currency}${balance}`;
|
||||
}
|
||||
|
||||
const envMap = {
|
||||
dev: {
|
||||
requestUrl: "https://apptest.devchat.ai",
|
||||
link: "https://webtest.devchat.ai",
|
||||
},
|
||||
prod: {
|
||||
requestUrl: "https://app.devchat.ai",
|
||||
link: "https://web.devchat.ai",
|
||||
},
|
||||
};
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/naming-convention
|
||||
export default function WechatTip() {
|
||||
const [bindWechat, setBindWechat] = useState(false);
|
||||
const [balance, setBalance] = useState<null | number>(null);
|
||||
const [currency, setCurrency] = useState("USD");
|
||||
const [accessKey, setAccessKey] = useState("");
|
||||
const [env, setEnv] = useState("prod");
|
||||
const [loading, setLoading] = useState(false);
|
||||
const platform = process.env.platform;
|
||||
|
||||
const getSettings = () => {
|
||||
messageUtil.sendMessage({
|
||||
command: "getUserAccessKey",
|
||||
});
|
||||
};
|
||||
|
||||
const getBalance = () => {
|
||||
if (!envMap[env].requestUrl || !accessKey) {
|
||||
return;
|
||||
}
|
||||
setLoading(true);
|
||||
axios
|
||||
.get(`${envMap[env].requestUrl}/api/v1/users/profile`, {
|
||||
headers: { Authorization: `Bearer ${accessKey}` },
|
||||
})
|
||||
.then((res) => {
|
||||
if (res?.data?.user?.wechat_nickname) {
|
||||
setBindWechat(true);
|
||||
}
|
||||
if (res?.data?.organization?.balance) {
|
||||
setBalance(formatBalance(res?.data?.organization?.balance));
|
||||
setCurrency(res?.data?.organization?.currency);
|
||||
}
|
||||
})
|
||||
.finally(() => {
|
||||
setLoading(false);
|
||||
});
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
if (env && accessKey) {
|
||||
getBalance();
|
||||
}
|
||||
}, [env, accessKey]);
|
||||
|
||||
useEffect(() => {
|
||||
getSettings();
|
||||
messageUtil.registerHandler(
|
||||
"getUserAccessKey",
|
||||
(message: { endPoint: string; accessKey: string; keyType: string }) => {
|
||||
if (message.keyType === "DevChat" && message.accessKey) {
|
||||
if (message.endPoint.includes("api-test.devchat.ai")) {
|
||||
setEnv("dev");
|
||||
} else {
|
||||
setEnv("prod");
|
||||
}
|
||||
setAccessKey(message.accessKey);
|
||||
}
|
||||
}
|
||||
);
|
||||
}, []);
|
||||
|
||||
const openLink = (e) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
messageUtil.sendMessage({
|
||||
command: "openLink",
|
||||
url: envMap[env].link,
|
||||
});
|
||||
};
|
||||
|
||||
if (balance === null || balance === undefined) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<HoverCard
|
||||
shadow="lg"
|
||||
position="left"
|
||||
width="200"
|
||||
withArrow={true}
|
||||
styles={{
|
||||
arrow: {
|
||||
borderColor: "var(--vscode-menu-border)",
|
||||
},
|
||||
}}
|
||||
zIndex={999}
|
||||
>
|
||||
<HoverCard.Target>
|
||||
<div onMouseEnter={getBalance}>
|
||||
<ActionIcon size="sm">
|
||||
<IconWallet size="1.125rem" />
|
||||
</ActionIcon>
|
||||
</div>
|
||||
</HoverCard.Target>
|
||||
<HoverCard.Dropdown
|
||||
sx={{
|
||||
color: "var(--vscode-foreground)",
|
||||
borderColor: "var(--vscode-menu-border)",
|
||||
backgroundColor: "var(--vscode-menu-background)",
|
||||
}}
|
||||
>
|
||||
<Group style={{ width: "90%" }}>
|
||||
<Text size="sm">
|
||||
Your remaining credit is {formatCurrency(balance, currency)}. Sign
|
||||
in to{" "}
|
||||
{platform === "idea" ? (
|
||||
<Text td="underline" c="blue" onClick={(e) => openLink(e)}>
|
||||
web.devchat.ai{" "}
|
||||
</Text>
|
||||
) : (
|
||||
<a href={envMap[env].link} target="_blank">
|
||||
web.devchat.ai{" "}
|
||||
</a>
|
||||
)}
|
||||
to {bindWechat ? "purchase more tokens." : "earn additional ¥8"}
|
||||
</Text>
|
||||
<LoadingOverlay visible={loading} />
|
||||
</Group>
|
||||
</HoverCard.Dropdown>
|
||||
</HoverCard>
|
||||
);
|
||||
}
|
@ -1,69 +0,0 @@
|
||||
import * as React from 'react';
|
||||
|
||||
// Create interface extending SVGProps
|
||||
export interface TablerIconsProps extends Partial<Omit<React.SVGProps<SVGSVGElement>, 'stroke'>> {
|
||||
size?: number,
|
||||
stroke?: number
|
||||
}
|
||||
|
||||
export const IconMouseRightClick = (props: TablerIconsProps) => {
|
||||
const { color, size, style } = props;
|
||||
return (<svg width={size} height={size} style={style} viewBox="0 0 36 36" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M5.5 9C5.5 5.41015 8.41015 2.5 12 2.5H24C27.5899 2.5 30.5 5.41015 30.5 9V21C30.5 27.9036 24.9036 33.5 18 33.5C11.0964 33.5 5.5 27.9036 5.5 21V9Z" stroke={color} strokeWidth="3" />
|
||||
<path d="M7 15H29.5" stroke={color} strokeWidth="3" strokeLinecap="round" strokeLinejoin="round" />
|
||||
<rect x="18" y="4" width="11" height="10" fill={color} />
|
||||
</svg>);
|
||||
};
|
||||
|
||||
|
||||
export const IconGitBranchChecked = (props: TablerIconsProps) => {
|
||||
const { color, size, style } = props;
|
||||
return (<svg width={size} height={size} style={style} viewBox="0 0 36 36" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M9 12L9 24" stroke={color} strokeWidth="3" strokeLinecap="round" strokeLinejoin="round" />
|
||||
<path d="M27 14V14C27 16.599 24.8931 18.7059 22.2941 18.7059H14.2941C11.3703 18.7059 9 21.0761 9 24V24" stroke={color} strokeWidth="3" />
|
||||
<path d="M27 13.5C29.4853 13.5 31.5 11.4853 31.5 9C31.5 6.51472 29.4853 4.5 27 4.5C24.5147 4.5 22.5 6.51472 22.5 9C22.5 11.4853 24.5147 13.5 27 13.5Z" stroke={color} strokeWidth="3" strokeLinecap="round" strokeLinejoin="round" />
|
||||
<path d="M9 34C11.4853 34 13.5 31.9853 13.5 29.5C13.5 27.0147 11.4853 25 9 25C6.51472 25 4.5 27.0147 4.5 29.5C4.5 31.9853 6.51472 34 9 34Z" stroke={color} strokeWidth="3" strokeLinecap="round" strokeLinejoin="round" />
|
||||
<path d="M9 11C11.4853 11 13.5 8.98528 13.5 6.5C13.5 4.01472 11.4853 2 9 2C6.51472 2 4.5 4.01472 4.5 6.5C4.5 8.98528 6.51472 11 9 11Z" stroke={color} strokeWidth="3" strokeLinecap="round" strokeLinejoin="round" />
|
||||
<path d="M21 28L25.5 32.5L34 24" stroke={color} strokeWidth="3" strokeLinecap="round" strokeLinejoin="round" />
|
||||
</svg>);
|
||||
};
|
||||
|
||||
|
||||
export const IconGitBranch = (props: TablerIconsProps) => {
|
||||
const { color, size, style } = props;
|
||||
return (<svg width={size} height={size} style={style} viewBox="0 0 36 36" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M9 12L9 24" stroke={color} strokeWidth="3" strokeLinecap="round" strokeLinejoin="round" />
|
||||
<path d="M27 14V14C27 16.599 24.8931 18.7059 22.2941 18.7059H14.2941C11.3703 18.7059 9 21.0761 9 24V24" stroke={color} strokeWidth="3" />
|
||||
<path d="M27 13.5C29.4853 13.5 31.5 11.4853 31.5 9C31.5 6.51472 29.4853 4.5 27 4.5C24.5147 4.5 22.5 6.51472 22.5 9C22.5 11.4853 24.5147 13.5 27 13.5Z" stroke={color} strokeWidth="3" strokeLinecap="round" strokeLinejoin="round" />
|
||||
<path d="M9 34C11.4853 34 13.5 31.9853 13.5 29.5C13.5 27.0147 11.4853 25 9 25C6.51472 25 4.5 27.0147 4.5 29.5C4.5 31.9853 6.51472 34 9 34Z" stroke={color} strokeWidth="3" strokeLinecap="round" strokeLinejoin="round" />
|
||||
<path d="M9 11C11.4853 11 13.5 8.98528 13.5 6.5C13.5 4.01472 11.4853 2 9 2C6.51472 2 4.5 4.01472 4.5 6.5C4.5 8.98528 6.51472 11 9 11Z" stroke={color} strokeWidth="3" strokeLinecap="round" strokeLinejoin="round" />
|
||||
</svg>);
|
||||
};
|
||||
|
||||
export const IconShellCommand = (props: TablerIconsProps) => {
|
||||
const { color, size, style } = props;
|
||||
return (<svg width={size} height={size} style={style} viewBox="0 0 36 36" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M28.5 5.5H7.5C5.84315 5.5 4.5 6.7934 4.5 8.38889V28.6111C4.5 30.2066 5.84315 31.5 7.5 31.5H28.5C30.1569 31.5 31.5 30.2066 31.5 28.6111V8.38889C31.5 6.7934 30.1569 5.5 28.5 5.5Z" stroke={color} strokeWidth="3" strokeLinecap="round" strokeLinejoin="round" />
|
||||
<path d="M12 24L15 21L12 18" stroke={color} strokeWidth="3" strokeLinecap="round" strokeLinejoin="round" />
|
||||
<path d="M5.5 11H31" stroke={color} strokeWidth="3" />
|
||||
<path d="M20 24L24 24" stroke={color} strokeWidth="3" strokeLinecap="round" />
|
||||
</svg>);
|
||||
};
|
||||
|
||||
|
||||
export const IconBook = (props: TablerIconsProps) => {
|
||||
const { color, size, style } = props;
|
||||
return (<svg width={size} height={size} style={style} viewBox="0 0 36 36" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M2.75 5.25H12C15.3137 5.25 18 7.93628 18 11.25V30.5C18 28.0147 15.9853 27 13.5 27H2.75V5.25Z" stroke={color} strokeWidth="3" strokeLinejoin="round" />
|
||||
<path d="M33.25 5.25H24C20.6863 5.25 18 7.93628 18 11.25V30.5C18 28.0147 20.0147 27 22.5 27H33.25V5.25Z" stroke={color} strokeWidth="3" strokeLinejoin="round" />
|
||||
<path d="M8 11L13 11" stroke={color} strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" />
|
||||
<path d="M23 11L28 11" stroke={color} strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" />
|
||||
<path d="M8 16L13 16" stroke={color} strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" />
|
||||
<path d="M23 16L28 16" stroke={color} strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" />
|
||||
<path d="M8 21L13 21" stroke={color} strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" />
|
||||
<path d="M23 21L28 21" stroke={color} strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" />
|
||||
<rect x="15" y="26" width="6" height="6" rx="2" fill={color} />
|
||||
<path d="M14 29C14.8 29.4 15 29.8333 15 30V26.5H11.5V28.5C12 28.5 13.2 28.6 14 29Z" fill={color} />
|
||||
<path d="M22 29C21.2 29.4 21 29.8333 21 30V26.5H24.5V28.5C24 28.5 22.8 28.6 22 29Z" fill={color} />
|
||||
</svg>);
|
||||
};
|
@ -1,286 +0,0 @@
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import { Box, Button, Checkbox, Text, Radio, Textarea, createStyles } from '@mantine/core';
|
||||
import { useListState, useSetState } from '@mantine/hooks';
|
||||
import { useMst } from '@/views/stores/RootStore';
|
||||
import yaml from 'js-yaml';
|
||||
|
||||
const useStyles = createStyles((theme) => ({
|
||||
container:{
|
||||
padding:0,
|
||||
margin:0,
|
||||
},
|
||||
submit:{
|
||||
marginTop:theme.spacing.xs,
|
||||
marginRight:theme.spacing.xs,
|
||||
marginBottom:theme.spacing.xs,
|
||||
},
|
||||
cancel:{
|
||||
},
|
||||
button:{
|
||||
marginTop:theme.spacing.xs,
|
||||
marginRight:theme.spacing.xs,
|
||||
marginBottom:theme.spacing.xs,
|
||||
},
|
||||
checkbox:{
|
||||
marginTop:theme.spacing.xs,
|
||||
marginBottom:theme.spacing.xs,
|
||||
},
|
||||
label:{
|
||||
color:'var(--vscode-editor-foreground)',
|
||||
},
|
||||
radio:{
|
||||
marginTop:theme.spacing.xs,
|
||||
marginBottom:theme.spacing.xs,
|
||||
},
|
||||
editor:{
|
||||
backgroundColor: 'var(--vscode-input-background)',
|
||||
borderColor: 'var(--vscode-input-border)',
|
||||
color: 'var(--vscode-input-foreground)',
|
||||
},
|
||||
editorWrapper:{
|
||||
marginTop:theme.spacing.xs,
|
||||
marginBottom:theme.spacing.xs,
|
||||
}
|
||||
}));
|
||||
|
||||
interface Wdiget{
|
||||
id:string,
|
||||
value:string,
|
||||
title?:string,
|
||||
type:'editor'|'checkbox'|'radio'|'button'|'text'
|
||||
}
|
||||
|
||||
const ChatMark = ({ children,value,messageDone }) => {
|
||||
const {classes} = useStyles();
|
||||
const [widgets,widgetsHandlers] = useListState<Wdiget>();
|
||||
const {chat} = useMst();
|
||||
const [autoForm,setAutoForm] = useState(false); // if any widget is checkbox,radio or editor wdiget, the form is auto around them
|
||||
const values = value?yaml.load(value):{};
|
||||
const [disabled,setDisabled] = useState(messageDone||!!value);
|
||||
|
||||
const handleSubmit = () => {
|
||||
let formData = {};
|
||||
widgets.forEach((widget)=>{
|
||||
if(widget.type === 'text'
|
||||
|| widget.type === 'button'
|
||||
|| (widget.type === 'radio' && widget.value === 'unchecked')
|
||||
|| (widget.type === 'checkbox' && widget.value === 'unchecked')){
|
||||
// ignore
|
||||
return;
|
||||
}
|
||||
formData[widget.id] = widget.value;
|
||||
});
|
||||
chat.userInput(formData);
|
||||
};
|
||||
|
||||
const handleCancel = () => {
|
||||
chat.userInput({
|
||||
'form':'canceled'
|
||||
});
|
||||
};
|
||||
|
||||
const handleButtonClick = ({event,index}) => {
|
||||
const widget = widgets[index];
|
||||
widget['value'] = event.currentTarget.value;;
|
||||
widgetsHandlers.setItem(index,widget);
|
||||
chat.userInput({
|
||||
[widget['id']]:'clicked'
|
||||
});
|
||||
};
|
||||
|
||||
const handleCheckboxChange = ({event,index})=>{
|
||||
const widget = widgets[index];
|
||||
widget['value'] = event.currentTarget.checked?'checked':'unchecked';
|
||||
widgetsHandlers.setItem(index,widget);
|
||||
};
|
||||
const handleRadioChange = ({event,allValues})=>{
|
||||
widgetsHandlers.apply((item, index) => {
|
||||
if(allValues.includes(item.id)){
|
||||
if(item.id === event){
|
||||
item.value = 'checked';
|
||||
}else{
|
||||
item.value = 'unchecked';
|
||||
}
|
||||
}
|
||||
return item;
|
||||
});
|
||||
};
|
||||
const handleEditorChange = ({event,index})=>{
|
||||
const widget = widgets[index];
|
||||
widget['value'] = event.currentTarget.value;
|
||||
widgetsHandlers.setItem(index,widget);
|
||||
};
|
||||
|
||||
useEffect(()=>{
|
||||
|
||||
const lines = children.split('\n');
|
||||
let detectEditorId = '';
|
||||
let editorContentRecorder = '';
|
||||
|
||||
const textRegex = /^([^>].*)/; // Text widget
|
||||
const buttonRegex = /^>\s*\((.*?)\)\s*(.*)/; // Button widget
|
||||
const checkboxRegex = /^>\s*\[([x ]*)\]\((.*?)\)\s*(.*)/; // Checkbox widget
|
||||
const radioRegex = /^>\s*-\s*\((.*?)\)\s*(.*)/; // Radio button widget
|
||||
const editorRegex = /^>\s*\|\s*\((.*?)\)/; // Editor widget
|
||||
const editorContentRegex = /^>\s*(.*)/; // Editor widget
|
||||
|
||||
lines.forEach((line, index) => {
|
||||
|
||||
let match;
|
||||
|
||||
if (match = line.match(textRegex)) {
|
||||
widgetsHandlers.append({
|
||||
id:`text${index}`,
|
||||
type:'text',
|
||||
value:line,
|
||||
});
|
||||
} else if (match = line.match(buttonRegex)) {
|
||||
const [id, title] = match.slice(1);
|
||||
widgetsHandlers.append({
|
||||
id,
|
||||
title,
|
||||
type:'button',
|
||||
value:title,
|
||||
});
|
||||
} else if (match = line.match(checkboxRegex)) {
|
||||
const [status, id, title] = match.slice(1);
|
||||
widgetsHandlers.append({
|
||||
id,
|
||||
title,
|
||||
type:'checkbox',
|
||||
value: status === 'x'?'checked':'unchecked',
|
||||
});
|
||||
setAutoForm(true);
|
||||
} else if (match = line.match(radioRegex)) {
|
||||
const [id, title] = match.slice(1);
|
||||
widgetsHandlers.append({
|
||||
id,
|
||||
title,
|
||||
type:'radio',
|
||||
value:'unchecked',
|
||||
});
|
||||
setAutoForm(true);
|
||||
} else if (match = line.match(editorRegex)) {
|
||||
const [id] = match.slice(1);
|
||||
detectEditorId = id;
|
||||
widgetsHandlers.append({
|
||||
id,
|
||||
type:'editor',
|
||||
value: '',
|
||||
});
|
||||
setAutoForm(true);
|
||||
} else if(match = line.match(editorContentRegex)){
|
||||
const [content] = match.slice(1);
|
||||
editorContentRecorder += content + '\n';
|
||||
}
|
||||
// if next line is not editor, then end current editor
|
||||
const nextLine = index + 1 < lines.length? lines[index + 1]:null;
|
||||
if (detectEditorId && (!nextLine || !nextLine.startsWith('>'))) {
|
||||
// remove last \n
|
||||
editorContentRecorder = editorContentRecorder.substring(0, editorContentRecorder.length - 1);
|
||||
// apply editor content to widget
|
||||
((editorId,editorContent) => widgetsHandlers.apply((item)=>{
|
||||
if(item.id === editorId){
|
||||
item.value = editorContent;
|
||||
}
|
||||
return item;
|
||||
}))(detectEditorId,editorContentRecorder);
|
||||
// reset editor
|
||||
detectEditorId = '';
|
||||
editorContentRecorder = '';
|
||||
}
|
||||
});
|
||||
for (const key in values) {
|
||||
widgetsHandlers.apply((item)=>{
|
||||
if(item.id === key){
|
||||
item.value = values[key];
|
||||
}
|
||||
return item;
|
||||
});
|
||||
}
|
||||
},[]);
|
||||
|
||||
// Render markdown widgets
|
||||
const renderWidgets = (widgets) => {
|
||||
let radioGroupTemp:any = [];
|
||||
let radioValuesTemp:any = [];
|
||||
let wdigetsTemp:any = [];
|
||||
widgets.map((widget, index) => {
|
||||
if (widget.type === 'text') {
|
||||
wdigetsTemp.push(<Text key={index}>{widget.value}</Text>);
|
||||
} else if (widget.type === 'button') {
|
||||
wdigetsTemp.push(<Button
|
||||
className={classes.button}
|
||||
disabled={disabled}
|
||||
key={'widget'+index}
|
||||
size='xs'
|
||||
value={widget.value}
|
||||
onClick={event => handleButtonClick({event,index})}>
|
||||
{widget.title}
|
||||
</Button>);
|
||||
} else if (widget.type === 'checkbox') {
|
||||
wdigetsTemp.push(<Checkbox
|
||||
classNames={{root:classes.checkbox,label:classes.label}}
|
||||
disabled={disabled}
|
||||
key={'widget'+index}
|
||||
label={widget.title}
|
||||
checked={widget.value==='checked'}
|
||||
size='xs'
|
||||
onChange={event => handleCheckboxChange({event,index})}/>);
|
||||
} else if (widget.type === 'radio') {
|
||||
radioValuesTemp.push(widget.id);
|
||||
radioGroupTemp.push(<Radio
|
||||
classNames={{root:classes.radio,label:classes.label}}
|
||||
disabled={disabled}
|
||||
key={'widget'+index}
|
||||
label={widget.title}
|
||||
value={widget.id}
|
||||
size='xs' />);
|
||||
// if next widget is not radio, then end current group
|
||||
const nextWidget = index + 1 < widgets.length? widgets[index + 1]:null;
|
||||
if (!nextWidget || nextWidget.type !== 'radio') {
|
||||
const radioGroup = ((radios,allValues)=><Radio.Group
|
||||
key={`radio-group-${index}`}
|
||||
onChange={
|
||||
event => handleRadioChange({
|
||||
event,
|
||||
allValues
|
||||
})
|
||||
}>
|
||||
{radios}
|
||||
</Radio.Group>)(radioGroupTemp,radioValuesTemp);
|
||||
radioGroupTemp = [];
|
||||
radioValuesTemp = [];
|
||||
wdigetsTemp.push(radioGroup);
|
||||
}
|
||||
} else if (widget.type === 'editor') {
|
||||
wdigetsTemp.push(<Textarea
|
||||
disabled={disabled}
|
||||
autosize
|
||||
classNames={{wrapper:classes.editorWrapper,input:classes.editor}}
|
||||
key={'widget'+index}
|
||||
defaultValue={widget.value}
|
||||
maxRows={10}
|
||||
onChange={event => handleEditorChange({event,index})}/>);
|
||||
}
|
||||
});
|
||||
return wdigetsTemp;
|
||||
};
|
||||
|
||||
return (
|
||||
<Box className={classes.container}>
|
||||
{autoForm && !disabled
|
||||
?<form>
|
||||
{renderWidgets(widgets)}
|
||||
<Box>
|
||||
<Button className={classes.submit} size='xs' onClick={handleSubmit}>Submit</Button>
|
||||
<Button className={classes.cancel} size='xs' onClick={handleCancel}>Cancel</Button>
|
||||
</Box>
|
||||
</form>
|
||||
:renderWidgets(widgets)
|
||||
}
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
||||
export default ChatMark;
|
@ -1,95 +0,0 @@
|
||||
|
||||
import React, { useEffect, useState } from "react";
|
||||
import { keyframes } from "@emotion/react";
|
||||
import { Box, Container, Text } from "@mantine/core";
|
||||
import MessageBody from "@/views/components/MessageBody";
|
||||
import { observer } from "mobx-react-lite";
|
||||
import { useMst } from "@/views/stores/RootStore";
|
||||
import { Message } from "@/views/stores/ChatStore";
|
||||
import {fromMarkdown} from 'mdast-util-from-markdown';
|
||||
import {toMarkdown} from 'mdast-util-to-markdown';
|
||||
|
||||
const MessageBlink = observer(() => {
|
||||
const { chat } = useMst();
|
||||
|
||||
const blink = keyframes({
|
||||
'50%': { opacity: 0 },
|
||||
});
|
||||
|
||||
return <Text sx={{
|
||||
animation: `${blink} 0.5s infinite;`,
|
||||
width: 5,
|
||||
marginTop: '1em',
|
||||
backgroundColor: 'black',
|
||||
display: 'block'
|
||||
}}>|</Text>;
|
||||
});
|
||||
|
||||
const getBlocks = (message) => {
|
||||
const messageText = message || '';
|
||||
const regex = /```([\s\S]+?)```/g;
|
||||
|
||||
let match;
|
||||
let lastIndex = 0;
|
||||
const blocks: string[] = [];
|
||||
|
||||
while ((match = regex.exec(messageText))) {
|
||||
const unmatchedText = messageText.substring(lastIndex, match.index);
|
||||
const matchedText = match[0];
|
||||
blocks.push(unmatchedText, matchedText);
|
||||
lastIndex = regex.lastIndex;
|
||||
}
|
||||
|
||||
const unmatchedText = messageText.substring(lastIndex);
|
||||
blocks.push(unmatchedText);
|
||||
|
||||
return blocks;
|
||||
};
|
||||
|
||||
const CurrentMessage = observer((props: any) => {
|
||||
const { width } = props;
|
||||
const { chat } = useMst();
|
||||
const { messages, currentMessage, generating, responsed, hasDone } = chat;
|
||||
// split blocks
|
||||
const messageBlocks = fromMarkdown(currentMessage);
|
||||
const lastMessageBlocks = fromMarkdown(messages[messages.length - 1]?.message);
|
||||
const fixedCount = lastMessageBlocks.children.length;
|
||||
const receivedCount = messageBlocks.children.length;
|
||||
const renderBlocks = messageBlocks.children.splice(-1);
|
||||
|
||||
useEffect(() => {
|
||||
if (generating && (receivedCount - fixedCount >= 1 || !responsed)) {
|
||||
chat.updateLastMessage(toMarkdown({
|
||||
type: 'root',
|
||||
children: messageBlocks.children
|
||||
}));
|
||||
}
|
||||
}, [currentMessage, responsed, generating]);
|
||||
|
||||
useEffect(() => {
|
||||
if (hasDone) {
|
||||
chat.updateLastMessage(currentMessage);
|
||||
}
|
||||
}, [hasDone]);
|
||||
|
||||
return generating
|
||||
? <Box
|
||||
sx={{
|
||||
padding: 0,
|
||||
marginTop: -5,
|
||||
marginBottom: 50,
|
||||
width: width,
|
||||
pre: {
|
||||
margin: 0,
|
||||
whiteSpace: 'break-spaces'
|
||||
},
|
||||
}}>
|
||||
<MessageBody messageType="bot" temp={true} >
|
||||
{renderBlocks.length>0?toMarkdown(renderBlocks[0]):''}
|
||||
</MessageBody>
|
||||
<MessageBlink />
|
||||
</Box>
|
||||
: <></>;
|
||||
});
|
||||
|
||||
export default CurrentMessage;
|
@ -1,56 +0,0 @@
|
||||
import React from "react";
|
||||
import { Header, Avatar, Flex, Text, ActionIcon, createStyles } from "@mantine/core";
|
||||
import BalanceTip from "@/views/components/BalanceTip";
|
||||
import { IconSettings } from "@tabler/icons-react";
|
||||
// @ts-ignore
|
||||
import SvgAvatarDevChat from "../MessageAvatar/avatar_devchat.svg";
|
||||
import messageUtil from "@/util/MessageUtil";
|
||||
|
||||
const useStyles = createStyles((theme) => ({
|
||||
logoName:{
|
||||
color: 'var(--vscode-foreground)'
|
||||
}
|
||||
}));
|
||||
|
||||
export default function Head() {
|
||||
const {classes} = useStyles();
|
||||
const openSetting = () => {
|
||||
messageUtil.sendMessage({
|
||||
command: "doCommand",
|
||||
content: ["workbench.action.openSettings", "DevChat"],
|
||||
});
|
||||
};
|
||||
return (
|
||||
<Header
|
||||
height={40}
|
||||
style={{
|
||||
backgroundColor: "var(--vscode-sideBar-background)",
|
||||
borderBottom: '1px solid #ced4da',
|
||||
}}
|
||||
>
|
||||
<Flex justify="space-between" align="center" sx={{ padding: "0 10px" }}>
|
||||
<Flex
|
||||
gap="sm"
|
||||
justify="flex-start"
|
||||
align="center"
|
||||
style={{
|
||||
height: 40,
|
||||
}}
|
||||
>
|
||||
<Avatar color="indigo" size={25} radius="xl" src={SvgAvatarDevChat} />
|
||||
<Text weight="bold" className={classes.logoName}>DevChat</Text>
|
||||
</Flex>
|
||||
<Flex align="center" gap="xs" sx={{paddingRight:10}}>
|
||||
<div>
|
||||
<BalanceTip />
|
||||
</div>
|
||||
<div>
|
||||
<ActionIcon size='sm' onClick={openSetting}>
|
||||
<IconSettings size="1.125rem" />
|
||||
</ActionIcon>
|
||||
</div>
|
||||
</Flex>
|
||||
</Flex>
|
||||
</Header>
|
||||
);
|
||||
}
|
@ -1,101 +0,0 @@
|
||||
import { Accordion, Box, ActionIcon, ScrollArea, Center, Text } from "@mantine/core";
|
||||
import { IconX } from "@tabler/icons-react";
|
||||
import React from "react";
|
||||
import { observer } from "mobx-react-lite";
|
||||
import { useMst } from "@/views/stores/RootStore";
|
||||
import { ChatContext } from "@/views/stores/InputStore";
|
||||
|
||||
const InputContexts = observer(() => {
|
||||
const { input } = useMst();
|
||||
return (<Accordion
|
||||
variant="contained"
|
||||
chevronPosition="left"
|
||||
multiple={true}
|
||||
sx={{
|
||||
backgroundColor: 'var(--vscode-menu-background)',
|
||||
}}
|
||||
styles={{
|
||||
item: {
|
||||
borderColor: 'var(--vscode-menu-border)',
|
||||
backgroundColor: 'var(--vscode-menu-background)',
|
||||
'&[data-active]': {
|
||||
backgroundColor: 'var(--vscode-menu-background)',
|
||||
}
|
||||
},
|
||||
control: {
|
||||
height: 30,
|
||||
borderRadius: 3,
|
||||
backgroundColor: 'var(--vscode-menu-background)',
|
||||
'&[aria-expanded="true"]': {
|
||||
borderBottomLeftRadius: 0,
|
||||
borderBottomRightRadius: 0,
|
||||
},
|
||||
'&:hover': {
|
||||
backgroundColor: 'var(--vscode-menu-background)',
|
||||
}
|
||||
},
|
||||
chevron: {
|
||||
color: 'var(--vscode-menu-foreground)',
|
||||
},
|
||||
icon: {
|
||||
color: 'var(--vscode-menu-foreground)',
|
||||
},
|
||||
label: {
|
||||
color: 'var(--vscode-menu-foreground)',
|
||||
},
|
||||
panel: {
|
||||
color: 'var(--vscode-menu-foreground)',
|
||||
backgroundColor: 'var(--vscode-menu-background)',
|
||||
overflow: 'hidden',
|
||||
},
|
||||
content: {
|
||||
borderRadius: 3,
|
||||
backgroundColor: 'var(--vscode-menu-background)',
|
||||
}
|
||||
}}>
|
||||
{
|
||||
input.contexts.map((context, index: number) => {
|
||||
const { content, command, file, path } = context;
|
||||
return (
|
||||
<Accordion.Item key={`item-${index}`} value={`item-value-${index}`} >
|
||||
<Box sx={{
|
||||
display: 'flex', alignItems: 'center',
|
||||
backgroundColor: 'var(--vscode-menu-background)',
|
||||
}}>
|
||||
<Accordion.Control w={'calc(100% - 40px)'}>
|
||||
<Text truncate='end'>{command ? command : path}</Text>
|
||||
</Accordion.Control>
|
||||
<ActionIcon
|
||||
mr={8}
|
||||
size="sm"
|
||||
sx={{
|
||||
color: 'var(--vscode-menu-foreground)',
|
||||
'&:hover': {
|
||||
backgroundColor: 'var(--vscode-toolbar-activeBackground)'
|
||||
}
|
||||
}}
|
||||
onClick={() => {
|
||||
input.removeContext(index);
|
||||
}}>
|
||||
<IconX size="1rem" />
|
||||
</ActionIcon>
|
||||
</Box>
|
||||
<Accordion.Panel>
|
||||
<ScrollArea type="auto">
|
||||
{
|
||||
content
|
||||
? <pre style={{ overflowWrap: 'normal', fontSize: 'var(--vscode-editor-font-size)', margin: 0 }}>{content}</pre>
|
||||
: <Center>
|
||||
<Text c='gray.3'>No content</Text>
|
||||
</Center>
|
||||
}
|
||||
</ScrollArea>
|
||||
</Accordion.Panel>
|
||||
</Accordion.Item>
|
||||
);
|
||||
})
|
||||
}
|
||||
</Accordion>);
|
||||
});
|
||||
|
||||
export default InputContexts;
|
@ -1,111 +0,0 @@
|
||||
import React, { useEffect, useState } from "react";
|
||||
import { ActionIcon, Drawer, Text, Box, Flex, Divider } from "@mantine/core";
|
||||
import { IconClock, IconChevronDown } from "@tabler/icons-react";
|
||||
import { useDisclosure } from "@mantine/hooks";
|
||||
import messageUtil from "@/util/MessageUtil";
|
||||
import dayjs from "dayjs";
|
||||
|
||||
export default function Topic({ styleName }) {
|
||||
const [drawerOpened, { open: openDrawer, close: closeDrawer }] =
|
||||
useDisclosure(false);
|
||||
const [topicList, setTopicList] = useState([]);
|
||||
|
||||
useEffect(() => {
|
||||
messageUtil.sendMessage({
|
||||
command: "listTopics",
|
||||
});
|
||||
messageUtil.registerHandler("listTopics", (data) => {
|
||||
setTopicList(data);
|
||||
});
|
||||
}, []);
|
||||
|
||||
const showTopic = (root_prompt: any) => {
|
||||
closeDrawer();
|
||||
messageUtil.sendMessage({
|
||||
command: "getTopicDetail",
|
||||
topicHash: root_prompt.hash,
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<Drawer
|
||||
opened={drawerOpened}
|
||||
position="bottom"
|
||||
title="Devchat Topic"
|
||||
onClose={closeDrawer}
|
||||
overlayProps={{ opacity: 0.5, blur: 4 }}
|
||||
closeButtonProps={{ children: <IconChevronDown size="1rem" /> }}
|
||||
styles={{
|
||||
content: {
|
||||
background: "var(--vscode-sideBar-background)",
|
||||
color: "var(--vscode-editor-foreground)",
|
||||
overflowY: "auto",
|
||||
},
|
||||
header: {
|
||||
background: "var(--vscode-sideBar-background)",
|
||||
color: "var(--vscode-editor-foreground)",
|
||||
},
|
||||
}}
|
||||
>
|
||||
{topicList.map((item: any, index) => (
|
||||
<Box
|
||||
sx={{
|
||||
cursor: "pointer",
|
||||
}}
|
||||
onClick={() => showTopic(item?.root_prompt)}
|
||||
>
|
||||
<Flex justify="space-between" gap="sm">
|
||||
<Text
|
||||
fz="sm"
|
||||
fw={700}
|
||||
sx={{
|
||||
whiteSpace: "nowrap",
|
||||
overflow: "hidden",
|
||||
textOverflow: "ellipsis",
|
||||
flex: 1,
|
||||
}}
|
||||
>
|
||||
{item?.root_prompt.title}
|
||||
</Text>
|
||||
<Text
|
||||
fz="sm"
|
||||
c="dimmed"
|
||||
sx={{
|
||||
whiteSpace: "nowrap",
|
||||
overflow: "hidden",
|
||||
textOverflow: "ellipsis",
|
||||
}}
|
||||
>
|
||||
{dayjs(item?.latest_time * 1000).format("MM-DD HH:mm:ss")}
|
||||
</Text>
|
||||
</Flex>
|
||||
|
||||
<Text
|
||||
c="dimmed"
|
||||
fz="sm"
|
||||
sx={{
|
||||
whiteSpace: "nowrap",
|
||||
overflow: "hidden",
|
||||
textOverflow: "ellipsis",
|
||||
}}
|
||||
>
|
||||
{item?.root_prompt.responses?.[0]}
|
||||
</Text>
|
||||
{index !== topicList.length - 1 && (
|
||||
<Divider variant="solid" my={6} opacity="0.5" />
|
||||
)}
|
||||
</Box>
|
||||
))}
|
||||
</Drawer>
|
||||
<ActionIcon
|
||||
className={styleName}
|
||||
radius="xl"
|
||||
variant="default"
|
||||
onClick={openDrawer}
|
||||
>
|
||||
<IconClock size="1rem" />
|
||||
</ActionIcon>
|
||||
</>
|
||||
);
|
||||
}
|
@ -1,522 +0,0 @@
|
||||
import { useMantineTheme, Flex, Stack, ActionIcon, ScrollArea, Popover, Textarea, Text, Indicator, Drawer, Group, Button, Menu,createStyles } from "@mantine/core";
|
||||
import { useDisclosure, useResizeObserver } from "@mantine/hooks";
|
||||
import { IconGitBranch, IconSend, IconPaperclip, IconChevronDown, IconTextPlus, IconRobot } from "@tabler/icons-react";
|
||||
import React, { useState, useEffect } from "react";
|
||||
import { IconGitBranchChecked, IconShellCommand } from "@/views/components/ChatIcons";
|
||||
import messageUtil from '@/util/MessageUtil';
|
||||
import InputContexts from './InputContexts';
|
||||
import Topic from './Topic';
|
||||
import { observer } from "mobx-react-lite";
|
||||
import { useMst } from "@/views/stores/RootStore";
|
||||
import { ChatContext } from "@/views/stores/InputStore";
|
||||
import { Message } from "@/views/stores/ChatStore";
|
||||
|
||||
const useStyles = createStyles((theme) => ({
|
||||
actionIcon:{
|
||||
color: 'var(--vscode-dropdown-foreground)',
|
||||
borderColor:'var(--vscode-dropdown-border)',
|
||||
backgroundColor: 'var(--vscode-dropdown-background)',
|
||||
'&:hover':{
|
||||
color: 'var(--vscode-dropdown-foreground)',
|
||||
borderColor:'var(--vscode-dropdown-border)',
|
||||
backgroundColor: 'var(--vscode-dropdown-background)'
|
||||
},
|
||||
'&[data-disabled]': {
|
||||
borderColor: "transparent",
|
||||
backgroundColor: "#e9ecef",
|
||||
color: "#adb5bd",
|
||||
cursor: "not-allowed",
|
||||
backgroundImage: "none",
|
||||
pointervents: "none",
|
||||
}
|
||||
}
|
||||
}));
|
||||
|
||||
const InputMessage = observer((props: any) => {
|
||||
const {classes} = useStyles();
|
||||
const { input, chat } = useMst();
|
||||
const { contexts, menuOpend, menuType, currentMenuIndex, contextMenus, commandMenus,modelMenus } = input;
|
||||
const { generating } = chat;
|
||||
const showTopic = process.env.platform === 'idea';
|
||||
|
||||
const [drawerOpened, { open: openDrawer, close: closeDrawer }] = useDisclosure(false);
|
||||
|
||||
const theme = useMantineTheme();
|
||||
const [commandMenusNode, setCommandMenusNode] = useState<any>(null);
|
||||
const [inputRef, inputRect] = useResizeObserver();
|
||||
|
||||
const handleInputChange = (event: React.ChangeEvent<HTMLTextAreaElement>) => {
|
||||
const value = event.target.value;
|
||||
// if value start with '/' command show menu
|
||||
if (value.startsWith('/')) {
|
||||
input.openMenu('commands');
|
||||
input.setCurrentMenuIndex(0);
|
||||
} else {
|
||||
input.closeMenu();
|
||||
}
|
||||
input.setValue(value);
|
||||
};
|
||||
|
||||
const handleSendClick = (event: React.MouseEvent<HTMLButtonElement>) => {
|
||||
const inputValue = input.value;
|
||||
if (inputValue) {
|
||||
if (inputValue.trim() === '/help') {
|
||||
chat.helpMessage();
|
||||
} else {
|
||||
const text = inputValue;
|
||||
const chatContexts = contexts ? [...contexts].map((item) => ({ ...item })) : undefined;
|
||||
if (inputValue.trim().startsWith('/ask-code')) {
|
||||
chat.devchatAsk(text, chatContexts);
|
||||
} else{
|
||||
chat.commonMessage(text, chatContexts);
|
||||
}
|
||||
}
|
||||
// Clear the input field
|
||||
input.setValue('');
|
||||
input.clearContexts();
|
||||
event.preventDefault();
|
||||
}
|
||||
};
|
||||
|
||||
const handleContextClick = (contextName: string) => {
|
||||
// Process and send the message to the extension
|
||||
messageUtil.sendMessage({
|
||||
command: 'addContext',
|
||||
selected: contextName
|
||||
});
|
||||
};
|
||||
|
||||
const handleKeyDown = (event: React.KeyboardEvent<HTMLTextAreaElement>) => {
|
||||
if (menuOpend) {
|
||||
if (event.key === 'Escape') {
|
||||
input.closeMenu();
|
||||
}
|
||||
if (menuType === 'commands') {
|
||||
if (event.key === 'ArrowDown') {
|
||||
const newIndex = currentMenuIndex + 1;
|
||||
input.setCurrentMenuIndex(newIndex < commandMenusNode.length ? newIndex : 0);
|
||||
event.preventDefault();
|
||||
}
|
||||
if (event.key === 'ArrowUp') {
|
||||
const newIndex = currentMenuIndex - 1;
|
||||
input.setCurrentMenuIndex(newIndex < 0 ? commandMenusNode.length - 1 : newIndex);
|
||||
event.preventDefault();
|
||||
}
|
||||
if ((event.key === 'Enter' || event.key === 'Tab') && !event.shiftKey) {
|
||||
const commandNode = commandMenusNode[currentMenuIndex];
|
||||
const commandPattern = commandNode.props['data-pattern'];
|
||||
if (commandPattern === 'help') {
|
||||
chat.helpMessage();
|
||||
input.setValue('');
|
||||
} else {
|
||||
input.setValue(`/${commandPattern} `);
|
||||
}
|
||||
input.closeMenu();
|
||||
event.preventDefault();
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (event.key === 'Enter' && !event.shiftKey && !event.nativeEvent.isComposing) {
|
||||
handleSendClick(event as any);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const contextMenuIcon = (name: string) => {
|
||||
if (name === 'git diff --cached') {
|
||||
return <IconGitBranchChecked size={14} color='var(--vscode-menu-foreground)'/>;
|
||||
}
|
||||
if (name === 'git diff HEAD') {
|
||||
return <IconGitBranch size={14} color='var(--vscode-menu-foreground)'/>;
|
||||
}
|
||||
return <IconShellCommand size={14} color='var(--vscode-menu-foreground)'/>;
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
input.fetchContextMenus().then();
|
||||
input.fetchCommandMenus().then();
|
||||
input.fetchModelMenus().then();
|
||||
messageUtil.registerHandler('regCommandList', (message: { result: object[]}) => {
|
||||
input.updateCommands(message.result);
|
||||
});
|
||||
messageUtil.registerHandler('chatWithDevChat', (message: {command: string, message: string}) => {
|
||||
chat.commonMessage(message.message, []);
|
||||
});
|
||||
messageUtil.registerHandler('appendContext', (message: { command: string; context: string }) => {
|
||||
// context is a temp file path
|
||||
const match = /\|([^]+?)\]/.exec(message.context);
|
||||
// Process and send the message to the extension
|
||||
messageUtil.sendMessage({
|
||||
command: 'contextDetail',
|
||||
file: match && match[1],
|
||||
});
|
||||
});
|
||||
messageUtil.registerHandler('contextDetailResponse', (message: { command: string; file: string; result: string }) => {
|
||||
//result is a content json
|
||||
// 1. diff json structure
|
||||
// {
|
||||
// languageId: languageId,
|
||||
// path: fileSelected,
|
||||
// content: codeSelected
|
||||
// };
|
||||
// 2. command json structure
|
||||
// {
|
||||
// command: commandString,
|
||||
// content: stdout
|
||||
// };
|
||||
const context = JSON.parse(message.result);
|
||||
if (typeof context !== 'undefined' && context) {
|
||||
const chatContext = ChatContext.create({
|
||||
file: message.file,
|
||||
path: context.path,
|
||||
command: context.command,
|
||||
content: context.content,
|
||||
});
|
||||
input.newContext(chatContext);
|
||||
}
|
||||
});
|
||||
inputRef.current.focus();
|
||||
}, []);
|
||||
|
||||
const getModelShowName = (modelName:string)=>{
|
||||
const nameMap = {
|
||||
"gpt-3.5-turbo": "GPT-3.5",
|
||||
"gpt-3.5-turbo-1106": "GPT-3.5-1106",
|
||||
"gpt-3.5-turbo-16k": "GPT-3.5-16K",
|
||||
"gpt-4": "GPT-4",
|
||||
"gpt-4-1106-preview": "GPT-4-turbo",
|
||||
"claude-2": "CLAUDE-2"
|
||||
};
|
||||
if (modelName in nameMap){
|
||||
return nameMap[modelName];
|
||||
} else if(modelName.lastIndexOf('/') > -1){
|
||||
return modelName.substring(modelName.lastIndexOf('/')+1).toLocaleUpperCase();
|
||||
} else {
|
||||
return modelName.toUpperCase();
|
||||
}
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
let filtered;
|
||||
if (input.value) {
|
||||
filtered = commandMenus.filter((item) => `/${item.pattern}`.startsWith(input.value));
|
||||
} else {
|
||||
filtered = commandMenus;
|
||||
}
|
||||
const node = filtered.map(({ pattern, description, name }, index) => {
|
||||
return (
|
||||
<Flex
|
||||
key={`command-menus-${index}`}
|
||||
mih={40}
|
||||
gap="md"
|
||||
justify="flex-start"
|
||||
align="flex-start"
|
||||
direction="row"
|
||||
wrap="wrap"
|
||||
sx={{
|
||||
padding: '5px 0',
|
||||
'&:hover,&[aria-checked=true]': {
|
||||
cursor: 'pointer',
|
||||
color: 'var(--vscode-commandCenter-activeForeground)',
|
||||
backgroundColor: 'var(--vscode-commandCenter-activeBackground)'
|
||||
}
|
||||
}}
|
||||
onClick={() => {
|
||||
input.setValue(`/${pattern} `);
|
||||
input.closeMenu();
|
||||
}}
|
||||
aria-checked={index === currentMenuIndex}
|
||||
data-pattern={pattern}>
|
||||
<Stack spacing={0}
|
||||
sx={{
|
||||
paddingLeft: 10,
|
||||
}}>
|
||||
<Text sx={{
|
||||
fontSize: 'sm',
|
||||
fontWeight: 'bolder',
|
||||
color: 'var(--vscode-menu-foreground)'
|
||||
}}>
|
||||
/{pattern}
|
||||
</Text>
|
||||
<Text sx={{
|
||||
fontSize: 'sm',
|
||||
color: theme.colors.gray[6],
|
||||
}}>
|
||||
{description}
|
||||
</Text>
|
||||
</Stack>
|
||||
</Flex>);
|
||||
});
|
||||
setCommandMenusNode(node);
|
||||
if (node.length === 0) {
|
||||
input.closeMenu();
|
||||
}
|
||||
}, [input.value, commandMenus, currentMenuIndex]);
|
||||
|
||||
useEffect(() => {
|
||||
if (drawerOpened && (!contexts || contexts.length === 0)) {
|
||||
closeDrawer();
|
||||
}
|
||||
}, [contexts.length]);
|
||||
|
||||
const changeModel = (value) =>{
|
||||
chat.changeChatModel(value);
|
||||
messageUtil.sendMessage({
|
||||
command: "updateSetting",
|
||||
key1: "devchat",
|
||||
key2: "defaultModel",
|
||||
value: value,
|
||||
});
|
||||
};
|
||||
|
||||
const menuStyles = {
|
||||
arrow:{
|
||||
borderColor: 'var(--vscode-menu-border)',
|
||||
},
|
||||
dropdown:{
|
||||
borderColor: 'var(--vscode-menu-border)',
|
||||
backgroundColor: 'var(--vscode-menu-background)'
|
||||
},
|
||||
itemLabel:{
|
||||
color: 'var(--vscode-menu-foreground)'
|
||||
},
|
||||
item: {
|
||||
padding: 5,
|
||||
backgroundColor: 'var(--vscode-menu-background)',
|
||||
'&:hover,&[data-hovered=true]': {
|
||||
color: 'var(--vscode-commandCenter-activeForeground)',
|
||||
borderColor: 'var(--vscode-commandCenter-border)',
|
||||
backgroundColor: 'var(--vscode-commandCenter-activeBackground)'
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const buttonStyles = {
|
||||
root: {
|
||||
color: 'var(--vscode-dropdown-foreground)',
|
||||
borderColor:'var(--vscode-dropdown-border)',
|
||||
backgroundColor: 'var(--vscode-dropdown-background)',
|
||||
'&:hover':{
|
||||
color: 'var(--vscode-dropdown-foreground)',
|
||||
borderColor:'var(--vscode-dropdown-border)',
|
||||
backgroundColor: 'var(--vscode-dropdown-background)'
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<Stack
|
||||
spacing={0}
|
||||
sx={{
|
||||
padding:'0 5px'
|
||||
}}
|
||||
>
|
||||
<Group
|
||||
spacing={5}
|
||||
sx={{
|
||||
marginTop: 5
|
||||
}}
|
||||
>
|
||||
<Menu
|
||||
width={chat.chatPanelWidth-10}
|
||||
position='bottom-start'
|
||||
shadow="sm"
|
||||
withArrow
|
||||
styles={menuStyles}
|
||||
disabled={contextMenus.length === 0}
|
||||
>
|
||||
<Menu.Target>
|
||||
<ActionIcon
|
||||
radius="xl"
|
||||
variant="default"
|
||||
disabled={generating || chat.disabled}
|
||||
className={classes.actionIcon}
|
||||
>
|
||||
<IconTextPlus size="1rem" />
|
||||
</ActionIcon>
|
||||
</Menu.Target>
|
||||
<Menu.Dropdown>
|
||||
{[...contextMenus]
|
||||
.sort((a, b) => {
|
||||
if (a.name === '<custom command>') {
|
||||
return 1; // Placing '<custom command>' at the end
|
||||
} else if (b.name === '<custom command>') {
|
||||
return -1; // Placing '<custom command>' at the front
|
||||
} else {
|
||||
return (a.name || "").localeCompare(b.name || ""); // Sorting alphabetically for other cases
|
||||
}
|
||||
})
|
||||
.map(({ pattern, description, name }, index) => {
|
||||
return (
|
||||
<Menu.Item
|
||||
key={`contexts-menus-${index}`}
|
||||
icon={contextMenuIcon(name)}
|
||||
onClick={() => {
|
||||
handleContextClick(name);
|
||||
}}
|
||||
>
|
||||
{name}
|
||||
<Text sx={{fontSize: '9pt',color: theme.colors.gray[6],}}>
|
||||
{description}
|
||||
</Text>
|
||||
</Menu.Item>);
|
||||
})}
|
||||
</Menu.Dropdown>
|
||||
</Menu>
|
||||
<Menu
|
||||
position="bottom-start"
|
||||
withArrow
|
||||
shadow="md"
|
||||
styles={menuStyles}
|
||||
disabled={modelMenus.length === 0}
|
||||
>
|
||||
<Menu.Target>
|
||||
<Button
|
||||
disabled={generating || chat.disabled}
|
||||
variant="default"
|
||||
size="xs"
|
||||
radius="xl"
|
||||
leftIcon={<IconRobot size="1rem" />}
|
||||
styles={buttonStyles}
|
||||
>
|
||||
{getModelShowName(chat.chatModel)}
|
||||
</Button>
|
||||
</Menu.Target>
|
||||
<Menu.Dropdown>
|
||||
{modelMenus.map((modelName) => {
|
||||
return <Menu.Item onClick={() => changeModel(modelName)}>
|
||||
{getModelShowName(modelName)}
|
||||
</Menu.Item>;
|
||||
})}
|
||||
</Menu.Dropdown>
|
||||
</Menu>
|
||||
{
|
||||
showTopic && <Topic styleName={classes.actionIcon}/>
|
||||
}
|
||||
</Group>
|
||||
{contexts && contexts.length > 0 &&
|
||||
<Drawer
|
||||
opened={drawerOpened}
|
||||
onClose={closeDrawer}
|
||||
position="bottom"
|
||||
title="DevChat Contexts"
|
||||
overlayProps={{ opacity: 0.5, blur: 4 }}
|
||||
closeButtonProps={{ children: <IconChevronDown size="1rem" /> }}
|
||||
styles={{
|
||||
content: {
|
||||
background: 'var(--vscode-sideBar-background)',
|
||||
color: 'var(--vscode-editor-foreground)',
|
||||
},
|
||||
header: {
|
||||
background: 'var(--vscode-sideBar-background)',
|
||||
color: 'var(--vscode-editor-foreground)',
|
||||
}
|
||||
}}>
|
||||
<InputContexts />
|
||||
</Drawer >
|
||||
}
|
||||
<Popover
|
||||
position='top-start'
|
||||
shadow="sm"
|
||||
width={chat.chatPanelWidth-10}
|
||||
opened={menuOpend}
|
||||
onChange={() => {
|
||||
input.closeMenu();
|
||||
inputRef.current.focus();
|
||||
}}
|
||||
>
|
||||
<Popover.Target>
|
||||
<Textarea
|
||||
id='chat-textarea'
|
||||
disabled={generating || chat.disabled}
|
||||
value={input.value}
|
||||
ref={inputRef}
|
||||
onKeyDown={handleKeyDown}
|
||||
onChange={handleInputChange}
|
||||
autosize
|
||||
minRows={1}
|
||||
maxRows={10}
|
||||
radius="md"
|
||||
size="xs"
|
||||
sx={{
|
||||
pointerEvents: 'all' ,
|
||||
marginTop: 5,
|
||||
marginBottom: 5
|
||||
}}
|
||||
placeholder="Ask DevChat a question or type ‘/’ for workflow"
|
||||
styles={{
|
||||
rightSection: { alignItems: 'flex-end', marginBottom:'6px', marginRight: (contexts.length > 0 ? '24px' : '10px') },
|
||||
input: {
|
||||
fontSize: 'var(--vscode-editor-font-size)',
|
||||
backgroundColor: 'var(--vscode-input-background)',
|
||||
borderColor: 'var(--vscode-input-border)',
|
||||
color: 'var(--vscode-input-foreground)',
|
||||
'&[data-disabled]': {
|
||||
color: 'var(--vscode-disabledForeground)'
|
||||
}
|
||||
}
|
||||
}}
|
||||
rightSection={
|
||||
<>
|
||||
{contexts.length > 0 &&
|
||||
<Indicator label={contexts.length} size={12}>
|
||||
<ActionIcon
|
||||
size='md'
|
||||
radius="md"
|
||||
variant="default"
|
||||
disabled={generating || chat.disabled}
|
||||
onClick={openDrawer}
|
||||
className={classes.actionIcon}
|
||||
sx={{
|
||||
pointerEvents: 'all',
|
||||
'&[data-disabled]': {
|
||||
borderColor: 'var(--vscode-input-border)',
|
||||
backgroundColor: 'var(--vscode-toolbar-activeBackground)'
|
||||
}
|
||||
}}>
|
||||
<IconPaperclip size="1rem" />
|
||||
</ActionIcon>
|
||||
</Indicator>
|
||||
}
|
||||
<ActionIcon
|
||||
size='md'
|
||||
radius="md"
|
||||
variant="default"
|
||||
disabled={generating || chat.disabled}
|
||||
onClick={handleSendClick}
|
||||
className={classes.actionIcon}
|
||||
sx={{
|
||||
marginLeft: '10px',
|
||||
pointerEvents: 'all',
|
||||
backgroundColor:'#ED6A45',
|
||||
border:'0',
|
||||
color:'#FFFFFF',
|
||||
'&:hover': {
|
||||
backgroundColor:'#ED6A45',
|
||||
color:'#FFFFFF',
|
||||
opacity:0.7
|
||||
}
|
||||
}}>
|
||||
<IconSend size="1rem" />
|
||||
</ActionIcon>
|
||||
</>
|
||||
}
|
||||
/>
|
||||
</Popover.Target>
|
||||
<Popover.Dropdown
|
||||
sx={{
|
||||
padding: 0,
|
||||
color: 'var(--vscode-menu-foreground)',
|
||||
borderColor: 'var(--vscode-menu-border)',
|
||||
backgroundColor: 'var(--vscode-menu-background)'
|
||||
}}>
|
||||
<Text sx={{ padding: '5px 5px 5px 10px' }}>DevChat Workflows</Text>
|
||||
<ScrollArea.Autosize mah={240} type="always">
|
||||
{commandMenusNode}
|
||||
</ScrollArea.Autosize>
|
||||
</Popover.Dropdown>
|
||||
</Popover >
|
||||
</Stack>);
|
||||
});
|
||||
|
||||
export default InputMessage;
|
@ -1,14 +0,0 @@
|
||||
<svg width="64" height="64" viewBox="0 0 64 64" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<rect width="64" height="64" rx="32" fill="#E8471C"/>
|
||||
<g clip-path="url(#clip0_160_2053)">
|
||||
<path d="M18.5353 44.6661C19.2152 44.0906 19.9213 43.5822 20.6405 43.1384C20.7906 43.0462 20.7331 42.8131 20.5583 42.8026C20.014 42.771 19.4606 42.7657 18.8994 42.7868C18.6919 42.7947 18.517 42.6301 18.517 42.4193V41.4066C18.517 37.4293 21.7133 34.204 25.6549 34.204H38.3176C40.5911 34.204 42.6154 35.276 43.9219 36.946C45.6552 36.7721 47.1887 36.2098 48.7314 35.014C47.7056 33.182 46.1616 31.6846 44.3004 30.7232C46.2125 26.5181 49.5289 18.6755 49.4545 15.0183C49.3579 10.314 45.5703 8.4755 43.5238 8.14493C41.4956 7.70638 37.3061 8.03958 35.531 12.3896C34.0849 15.9336 34.4634 25.1221 34.7479 29.5472H29.2258C29.5103 25.1208 29.8888 15.9336 28.4427 12.3896C26.6664 8.03826 22.4768 7.70638 20.4486 8.14493C18.4008 8.4755 14.6133 10.314 14.518 15.0183C14.4436 18.6755 17.7613 26.5208 19.6734 30.7259C16.2264 32.5064 13.8667 36.1229 13.8667 40.2977V45.2495C13.8667 46.6442 14.1303 47.977 14.6093 49.2005C15.7553 47.3949 17.0917 45.883 18.5353 44.6661ZM38.6686 13.6921C39.2103 12.5423 39.9281 11.814 41.0519 11.5493C41.9394 11.3399 42.7342 11.4782 42.8386 11.498L42.8713 11.5085L42.9587 11.5204C43.0422 11.5362 43.8462 11.6942 44.6097 12.2197C45.5625 12.8768 46.0376 13.8145 46.0637 15.0867C46.0832 16.0165 45.7256 18.5267 43.1166 24.8864C42.3727 26.7012 41.6209 28.3961 41.0414 29.6657C40.642 29.5643 39.9621 29.5472 39.9621 29.5472C39.9621 29.5472 43.7823 18.8717 42.7251 16.9529C41.4225 14.5889 38.1414 15.5016 38.1414 15.5016C38.1414 15.5016 38.2875 14.502 38.6699 13.6907L38.6686 13.6921ZM24.7491 29.5472C24.7491 29.5472 24.9736 18.1382 23.0772 16.4603C20.4788 14.2979 18.0928 16.5275 18.0928 16.5275C17.9414 15.7663 17.9049 15.4028 17.9101 15.0881C17.9362 13.8159 18.4113 12.8768 19.364 12.221C20.1158 11.7021 20.9067 11.5414 21.0111 11.5217H21.0451L21.1299 11.4993C21.2121 11.4835 22.0187 11.3373 22.9206 11.5493C24.0443 11.814 24.8235 12.5147 25.3038 13.6921C25.6549 14.552 26.22 17.0227 26.0686 23.9039C26.0203 26.0769 25.9107 28.1183 25.8193 29.5472H24.7491Z" fill="white"/>
|
||||
<path d="M49.3631 36.3467C48.2171 38.1523 46.8807 39.6642 45.4371 40.881C44.7572 41.4566 44.0511 41.9649 43.3319 42.4087C43.1818 42.5009 43.2393 42.7354 43.4141 42.7446C43.9584 42.7762 44.5118 42.7815 45.073 42.7604C45.2805 42.7525 45.4554 42.9171 45.4554 43.1278V44.1406C45.4554 48.1179 42.2591 51.3431 38.3175 51.3431H25.6548C23.3812 51.3431 21.3569 50.2711 20.0505 48.6012C18.3172 48.775 16.7837 49.3374 15.241 50.5332C17.0695 53.7967 20.5399 56 24.5207 56H39.4517C45.3353 56 50.1057 51.1864 50.1057 45.2495V40.2976C50.1057 38.9029 49.8421 37.5702 49.3631 36.3467Z" fill="white"/>
|
||||
<path d="M26.0502 46.1082C27.4789 46.1082 28.637 44.6152 28.637 42.7736C28.637 40.9319 27.4789 39.439 26.0502 39.439C24.6215 39.439 23.4634 40.9319 23.4634 42.7736C23.4634 44.6152 24.6215 46.1082 26.0502 46.1082Z" fill="white"/>
|
||||
<path d="M37.9233 46.1082C39.3519 46.1082 40.5101 44.6152 40.5101 42.7736C40.5101 40.9319 39.3519 39.439 37.9233 39.439C36.4946 39.439 35.3364 40.9319 35.3364 42.7736C35.3364 44.6152 36.4946 46.1082 37.9233 46.1082Z" fill="white"/>
|
||||
</g>
|
||||
<defs>
|
||||
<clipPath id="clip0_160_2053">
|
||||
<rect width="36.2391" height="48" fill="white" transform="translate(13.8667 8)"/>
|
||||
</clipPath>
|
||||
</defs>
|
||||
</svg>
|
Before Width: | Height: | Size: 3.3 KiB |
Binary file not shown.
Before Width: | Height: | Size: 105 KiB |
File diff suppressed because one or more lines are too long
Before Width: | Height: | Size: 104 KiB |
@ -1,111 +0,0 @@
|
||||
import React from "react";
|
||||
import { Text, Flex, Avatar, ActionIcon, Tooltip, CopyButton, SimpleGrid } from "@mantine/core";
|
||||
|
||||
// @ts-ignore
|
||||
import SvgAvatarDevChat from './avatar_devchat.svg';
|
||||
// @ts-ignore
|
||||
import SvgAvatarUser from './avatar_spaceman.png';
|
||||
import { IconCheck, IconCopy, Icon360, IconEdit, IconTrash } from "@tabler/icons-react";
|
||||
import { observer } from "mobx-react-lite";
|
||||
import { useMst } from "@/views/stores/RootStore";
|
||||
|
||||
import { IMessage } from "@/views/stores/ChatStore";
|
||||
import { IChatContext } from "@/views/stores/InputStore";
|
||||
|
||||
interface IProps {
|
||||
item?: IMessage,
|
||||
avatarType?: string,
|
||||
copyMessage?: string,
|
||||
messageContexts?: IChatContext[],
|
||||
deleteHash?: string,
|
||||
showEdit?: boolean,
|
||||
showDelete?: boolean
|
||||
}
|
||||
|
||||
const MessageAvatar = observer((props: IProps) => {
|
||||
const {
|
||||
messageContexts = [],
|
||||
copyMessage = "",
|
||||
deleteHash = undefined,
|
||||
avatarType = "user",
|
||||
showEdit = false,
|
||||
showDelete = false
|
||||
} = props;
|
||||
const { input, chat } = useMst();
|
||||
const [done, setDone] = React.useState(false);
|
||||
return (<Flex
|
||||
m='10px 0 10px 0'
|
||||
gap="sm"
|
||||
justify="flex-start"
|
||||
align="center"
|
||||
direction="row"
|
||||
wrap="wrap">
|
||||
{
|
||||
avatarType === 'bot'
|
||||
? <Avatar
|
||||
color="indigo"
|
||||
size={25}
|
||||
radius="xl"
|
||||
src={SvgAvatarDevChat} />
|
||||
: <Avatar
|
||||
color="cyan"
|
||||
size={25}
|
||||
radius="xl"
|
||||
src={SvgAvatarUser} />
|
||||
}
|
||||
<Text weight='bold'>{avatarType === 'bot' ? 'DevChat' : 'User'}</Text>
|
||||
{avatarType === 'user'
|
||||
? <Flex
|
||||
gap="xs"
|
||||
justify="flex-end"
|
||||
align="center"
|
||||
direction="row"
|
||||
wrap="wrap"
|
||||
style={{ marginLeft: 'auto', marginRight: '10px' }}>
|
||||
<Tooltip sx={{ padding: '3px', fontSize: 'var(--vscode-editor-font-size)' }} label={done ? 'Refilled' : 'Refill prompt'} withArrow position="left" color="gray">
|
||||
<ActionIcon size='sm'
|
||||
onClick={() => {
|
||||
input.setValue(copyMessage);
|
||||
input.setContexts(messageContexts);
|
||||
setDone(true);
|
||||
setTimeout(() => { setDone(false); }, 2000);
|
||||
}}>
|
||||
{done ? <IconCheck size="1rem" /> : <Icon360 size="1.125rem" />}
|
||||
</ActionIcon>
|
||||
</Tooltip>
|
||||
{showEdit && <Tooltip sx={{ padding: '3px', fontSize: 'var(--vscode-editor-font-size)' }} label="Edit message" withArrow position="left" color="gray">
|
||||
<ActionIcon size='sm'
|
||||
onClick={() => {
|
||||
|
||||
}}>
|
||||
<IconEdit size="1.125rem" />
|
||||
</ActionIcon>
|
||||
</Tooltip >}
|
||||
{showDelete && deleteHash !== 'message' && <Tooltip sx={{ padding: '3px', fontSize: 'var(--vscode-editor-font-size)' }} label="Delete message" withArrow position="left" color="gray">
|
||||
<ActionIcon size='sm'
|
||||
onClick={() => {
|
||||
if (deleteHash) {
|
||||
chat.deleteMessage(deleteHash).then();
|
||||
} else {
|
||||
chat.popMessage();
|
||||
chat.popMessage();
|
||||
}
|
||||
}}>
|
||||
<IconTrash size="1.125rem" />
|
||||
</ActionIcon>
|
||||
</Tooltip >}
|
||||
</Flex >
|
||||
: <CopyButton value={copyMessage} timeout={2000}>
|
||||
{({ copied, copy }) => (
|
||||
<Tooltip sx={{ padding: '3px', fontSize: 'var(--vscode-editor-font-size)' }} label={copied ? 'Copied' : 'Copy message'} withArrow position="left" color="gray">
|
||||
<ActionIcon size='xs' color={copied ? 'teal' : 'gray'} onClick={copy} style={{ marginLeft: 'auto', marginRight: '10px' }}>
|
||||
{copied ? <IconCheck size="1rem" /> : <IconCopy size="1rem" />}
|
||||
</ActionIcon>
|
||||
</Tooltip>
|
||||
)}
|
||||
</CopyButton>
|
||||
}
|
||||
</Flex >);
|
||||
});
|
||||
|
||||
export default MessageAvatar;
|
@ -1,48 +0,0 @@
|
||||
import { Container, createStyles ,Text} from "@mantine/core";
|
||||
import React from "react";
|
||||
import { observer } from "mobx-react-lite";
|
||||
import MessageMarkdown from "@/views/components/MessageMarkdown";
|
||||
import { useMst } from "@/views/stores/RootStore";
|
||||
|
||||
interface IProps {
|
||||
messageType: string,
|
||||
children: string,
|
||||
messageDone?: boolean,
|
||||
temp?: boolean
|
||||
}
|
||||
|
||||
|
||||
const useStyles = createStyles((theme, options:any) => ({
|
||||
bodyWidth:{
|
||||
width: options.chatPanelWidth - 20,
|
||||
},
|
||||
userContent:{
|
||||
fontFamily: theme.fontFamily,
|
||||
whiteSpace: 'pre-wrap',
|
||||
wordBreak: 'break-word',
|
||||
}
|
||||
}));
|
||||
|
||||
const MessageBody = observer((props: IProps) => {
|
||||
const { children, messageType, temp=false ,messageDone} = props;
|
||||
const { chat } = useMst();
|
||||
const {classes} = useStyles({
|
||||
chatPanelWidth:chat.chatPanelWidth
|
||||
});
|
||||
return (
|
||||
messageType === 'bot'
|
||||
? <MessageMarkdown className={classes.bodyWidth} temp={temp} messageDone={messageDone}>
|
||||
{children}
|
||||
</MessageMarkdown>
|
||||
: <Container
|
||||
sx={{
|
||||
margin: 0,
|
||||
padding: 0,
|
||||
width: chat.chatPanelWidth - 20
|
||||
}}>
|
||||
<pre className={classes.userContent}>{children}</pre>
|
||||
</Container>
|
||||
);
|
||||
});
|
||||
|
||||
export default MessageBody;
|
@ -1,91 +0,0 @@
|
||||
|
||||
|
||||
import { IInputStore } from "@/views/stores/InputStore";
|
||||
import { Accordion, Box, Center, Text } from "@mantine/core";
|
||||
import { observer } from "mobx-react-lite";
|
||||
import { useMst } from "@/views/stores/RootStore";
|
||||
import React from "react";
|
||||
|
||||
interface IProps {
|
||||
contexts?: IInputStore['contexts'];
|
||||
}
|
||||
|
||||
const MessageContext = observer(({ contexts }: IProps) => {
|
||||
const { chat } = useMst();
|
||||
return (<>
|
||||
{
|
||||
contexts &&
|
||||
<Accordion variant="contained" chevronPosition="left"
|
||||
sx={{
|
||||
marginTop: 5,
|
||||
borderRadius: 5,
|
||||
backgroundColor: 'var(--vscode-menu-background)',
|
||||
}}
|
||||
styles={{
|
||||
item: {
|
||||
borderColor: 'var(--vscode-menu-border)',
|
||||
backgroundColor: 'var(--vscode-menu-background)',
|
||||
'&[data-active]': {
|
||||
backgroundColor: 'var(--vscode-menu-background)',
|
||||
}
|
||||
},
|
||||
control: {
|
||||
height: 30,
|
||||
borderRadius: 3,
|
||||
backgroundColor: 'var(--vscode-menu-background)',
|
||||
'&[aria-expanded="true"]': {
|
||||
borderBottomLeftRadius: 0,
|
||||
borderBottomRightRadius: 0,
|
||||
},
|
||||
'&:hover': {
|
||||
backgroundColor: 'var(--vscode-menu-background)',
|
||||
}
|
||||
},
|
||||
chevron: {
|
||||
color: 'var(--vscode-menu-foreground)',
|
||||
},
|
||||
icon: {
|
||||
color: 'var(--vscode-menu-foreground)',
|
||||
},
|
||||
label: {
|
||||
color: 'var(--vscode-menu-foreground)',
|
||||
},
|
||||
panel: {
|
||||
color: 'var(--vscode-menu-foreground)',
|
||||
backgroundColor: 'var(--vscode-menu-background)',
|
||||
},
|
||||
content: {
|
||||
borderRadius: 3,
|
||||
backgroundColor: 'var(--vscode-menu-background)',
|
||||
}
|
||||
}}>
|
||||
{
|
||||
contexts?.map((item, index: number) => {
|
||||
const { content, command, file, path } = item;
|
||||
return (
|
||||
<Accordion.Item key={`item-${index}`} value={`item-value-${index}`} mah='200'>
|
||||
<Box sx={{ display: 'flex', alignItems: 'center' }}>
|
||||
<Accordion.Control >
|
||||
<Text truncate='end' w={chat.chatPanelWidth-100}>{command ? command : path}</Text>
|
||||
</Accordion.Control>
|
||||
</Box>
|
||||
<Accordion.Panel>
|
||||
{
|
||||
content
|
||||
? <pre style={{ overflowWrap: 'normal' }}>{content}</pre>
|
||||
: <Center>
|
||||
<Text c='gray.3'>No content</Text>
|
||||
</Center>
|
||||
}
|
||||
|
||||
</Accordion.Panel>
|
||||
</Accordion.Item>
|
||||
);
|
||||
})
|
||||
}
|
||||
</Accordion>
|
||||
}
|
||||
</>);
|
||||
});
|
||||
|
||||
export default MessageContext;
|
@ -1,157 +0,0 @@
|
||||
import {
|
||||
Stack,
|
||||
Container,
|
||||
Divider,
|
||||
Box,
|
||||
Group,
|
||||
Text,
|
||||
Button,
|
||||
createStyles,
|
||||
} from "@mantine/core";
|
||||
import React, { useEffect } from "react";
|
||||
import MessageBody from "@/views/components/MessageBody";
|
||||
import MessageAvatar from "@/views/components/MessageAvatar";
|
||||
import { observer } from "mobx-react-lite";
|
||||
import { useMst } from "@/views/stores/RootStore";
|
||||
import { Message } from "@/views/stores/ChatStore";
|
||||
import MessageContext from "@/views/components/MessageContext";
|
||||
import CurrentMessage from "@/views/components/CurrentMessage";
|
||||
import { Card } from "@mantine/core";
|
||||
import { IconInfoSquareRounded } from "@tabler/icons-react";
|
||||
|
||||
const useStyles = createStyles((theme) => ({
|
||||
card: {
|
||||
backgroundColor: "var(--vscode-menu-background)",
|
||||
fontFamily: "var(--vscode-editor-font-familyy)",
|
||||
fontSize: "var(--vscode-editor-font-size)",
|
||||
color: "var(--vscode-menu-foreground)",
|
||||
borderColor: "var(--vscode-menu-border)",
|
||||
},
|
||||
cardDescription: {
|
||||
marginTop: 10,
|
||||
marginBottom: 10,
|
||||
},
|
||||
button: {
|
||||
backgroundColor: "#ED6A45",
|
||||
fontFamily: "var(--vscode-editor-font-familyy)",
|
||||
fontSize: "var(--vscode-editor-font-size)",
|
||||
color: "#fff",
|
||||
"&:hover": {
|
||||
backgroundColor: "#ED6A45",
|
||||
opacity: 0.8,
|
||||
},
|
||||
"&:focus": {
|
||||
backgroundColor: "#ED6A45",
|
||||
opacity: 0.8,
|
||||
},
|
||||
},
|
||||
}));
|
||||
|
||||
const MessageList = observer((props: any) => {
|
||||
const { chat } = useMst();
|
||||
const { classes } = useStyles();
|
||||
|
||||
return (
|
||||
<Stack spacing={0} sx={{ margin: "0 10px 10px 10px" }}>
|
||||
{chat.messages.map((item, index: number) => {
|
||||
const {
|
||||
message: messageText,
|
||||
type: messageType,
|
||||
hash: messageHash,
|
||||
contexts,
|
||||
confirm,
|
||||
} = item;
|
||||
// setMessage(messageText);
|
||||
return (
|
||||
<Stack
|
||||
spacing={0}
|
||||
key={`message-${index}`}
|
||||
sx={{
|
||||
padding: 0,
|
||||
margin: 0,
|
||||
}}
|
||||
>
|
||||
<MessageAvatar
|
||||
key={`message-header-${index}`}
|
||||
showDelete={index === chat.messages.length - 2}
|
||||
deleteHash={messageHash}
|
||||
avatarType={messageType}
|
||||
copyMessage={messageText}
|
||||
messageContexts={contexts}
|
||||
/>
|
||||
<Box
|
||||
key={`message-container-${index}`}
|
||||
sx={{
|
||||
margin: 0,
|
||||
padding: 0,
|
||||
pre: {
|
||||
whiteSpace: "break-spaces",
|
||||
},
|
||||
}}
|
||||
>
|
||||
{messageType === "bot" && confirm && (
|
||||
<Card
|
||||
shadow="sm"
|
||||
padding="xs"
|
||||
radius="md"
|
||||
withBorder
|
||||
className={classes.card}
|
||||
>
|
||||
<Card.Section withBorder inheritPadding py="xs">
|
||||
<Group position="left">
|
||||
<IconInfoSquareRounded size={20} />
|
||||
<Text fw={500}>GPT-4 Usage Required</Text>
|
||||
</Group>
|
||||
</Card.Section>
|
||||
<Text className={classes.cardDescription}>
|
||||
DevChat will make GPT-4 API calls to analyze up to ten
|
||||
source files, costing{" "}
|
||||
<strong>approximately $0.4 USD per question</strong>.
|
||||
<br />
|
||||
<br />
|
||||
Would you like to proceed?
|
||||
</Text>
|
||||
<Group position="right">
|
||||
<Button
|
||||
size="compact-xs"
|
||||
className={classes.button}
|
||||
onClick={() => chat.sendLastUserMessage()}
|
||||
>
|
||||
Yes
|
||||
</Button>
|
||||
<Button
|
||||
size="compact-xs"
|
||||
className={classes.button}
|
||||
onClick={() => chat.cancelDevchatAsk()}
|
||||
>
|
||||
No
|
||||
</Button>
|
||||
</Group>
|
||||
</Card>
|
||||
)}
|
||||
<MessageContext
|
||||
key={`message-context-${index}`}
|
||||
contexts={contexts}
|
||||
/>
|
||||
<MessageBody
|
||||
key={`message-codeblock-${index}`}
|
||||
messageType={messageType}
|
||||
messageDone={
|
||||
!(index === chat.messages.length - 1 && chat.generating)
|
||||
}
|
||||
>
|
||||
{messageText}
|
||||
</MessageBody>
|
||||
</Box>
|
||||
{index !== chat.messages.length - 1 && (
|
||||
<Divider my={3} key={`message-divider-${index}`} />
|
||||
)}
|
||||
</Stack>
|
||||
);
|
||||
})}
|
||||
<CurrentMessage />
|
||||
</Stack>
|
||||
);
|
||||
});
|
||||
|
||||
export default MessageList;
|
@ -1,109 +0,0 @@
|
||||
import { Tooltip, ActionIcon, CopyButton, Flex } from "@mantine/core";
|
||||
import { IconCheck, IconGitCommit, IconFileDiff, IconColumnInsertRight, IconReplace, IconCopy } from "@tabler/icons-react";
|
||||
import React, { useState } from "react";
|
||||
|
||||
import messageUtil from '@/util/MessageUtil';
|
||||
|
||||
const IconButton = ({ label, color = 'gray', onClick, children }) => (
|
||||
<Tooltip sx={{ padding: '3px', fontSize: 'var(--vscode-editor-font-size)' }} label={label} withArrow position="left" color="gray">
|
||||
<ActionIcon size='xs' color={color} onClick={onClick}>
|
||||
{children}
|
||||
</ActionIcon>
|
||||
</Tooltip>
|
||||
);
|
||||
|
||||
const CommitButton = ({ code }) => {
|
||||
const [commited, setCommited] = useState(false);
|
||||
const handleClick = () => {
|
||||
messageUtil.sendMessage({
|
||||
command: 'doCommit',
|
||||
content: code
|
||||
});
|
||||
setCommited(true);
|
||||
setTimeout(() => { setCommited(false); }, 2000);
|
||||
};
|
||||
return (
|
||||
<IconButton label={commited ? 'Committing' : 'Commit'} color={commited ? 'teal' : 'gray'} onClick={handleClick}>
|
||||
{commited ? <IconCheck size="1rem" /> : <IconGitCommit size="1rem" />}
|
||||
</IconButton>
|
||||
);
|
||||
};
|
||||
|
||||
const CodeCopyButton = ({ code }) => {
|
||||
return (
|
||||
<CopyButton value={code} timeout={2000}>
|
||||
{({ copied, copy }) => (
|
||||
<IconButton label={copied ? 'Copied' : 'Copy'} color={copied ? 'teal' : 'gray'} onClick={copy}>
|
||||
{copied ? <IconCheck size="1rem" /> : <IconCopy size="1rem" />}
|
||||
</IconButton>
|
||||
)}
|
||||
</CopyButton>
|
||||
);
|
||||
};
|
||||
|
||||
const DiffButton = ({ code }) => {
|
||||
const handleClick = () => {
|
||||
messageUtil.sendMessage({
|
||||
command: 'show_diff',
|
||||
content: code
|
||||
});
|
||||
};
|
||||
return (
|
||||
<IconButton label='View Diff' onClick={handleClick}>
|
||||
<IconFileDiff size="1.125rem" />
|
||||
</IconButton>
|
||||
);
|
||||
};
|
||||
|
||||
const CodeApplyButton = ({ code }) => {
|
||||
const handleClick = () => {
|
||||
messageUtil.sendMessage({
|
||||
command: 'code_apply',
|
||||
content: code
|
||||
});
|
||||
};
|
||||
return (
|
||||
<IconButton label='Insert Code' onClick={handleClick}>
|
||||
<IconColumnInsertRight size="1.125rem" />
|
||||
</IconButton>
|
||||
);
|
||||
};
|
||||
|
||||
const FileApplyButton = ({ code }) => {
|
||||
const handleClick = () => {
|
||||
messageUtil.sendMessage({
|
||||
command: 'code_file_apply',
|
||||
content: code
|
||||
});
|
||||
};
|
||||
return (
|
||||
<IconButton label='Replace File' onClick={handleClick}>
|
||||
<IconReplace size="1.125rem" />
|
||||
</IconButton>
|
||||
);
|
||||
};
|
||||
|
||||
// Similar changes can be made to DiffButton, CodeApplyButton, FileApplyButton, and CodeCopyButton
|
||||
const CodeButtons = ({ language, code }) => (
|
||||
<Flex
|
||||
gap="5px"
|
||||
justify="flex-start"
|
||||
align="flex-start"
|
||||
direction="row"
|
||||
wrap="wrap"
|
||||
style={{ position: 'absolute', top: 8, right: 10 }}
|
||||
>
|
||||
<CodeCopyButton code={code} />
|
||||
{language && language === 'commitmsg'
|
||||
? <CommitButton code={code} />
|
||||
: (
|
||||
<>
|
||||
<DiffButton code={code} />
|
||||
<CodeApplyButton code={code} />
|
||||
<FileApplyButton code={code} />
|
||||
</>
|
||||
)}
|
||||
</Flex>
|
||||
);
|
||||
|
||||
export default CodeButtons;
|
@ -1,24 +0,0 @@
|
||||
import React from 'react';
|
||||
|
||||
interface LanguageCornerProps {
|
||||
language: string;
|
||||
}
|
||||
const LanguageCorner: React.FC<LanguageCornerProps> = ({ language }) => {
|
||||
return (
|
||||
<div style={{ position: 'absolute', top: 0, left: 0 }}>
|
||||
{language && (
|
||||
<div style={{
|
||||
backgroundColor: '#333',
|
||||
color: '#fff',
|
||||
padding: '0.2rem 0.5rem',
|
||||
borderRadius: '0.2rem',
|
||||
fontSize: '0.8rem',
|
||||
}}>
|
||||
{language}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default LanguageCorner;
|
@ -1,106 +0,0 @@
|
||||
import { Accordion, Box, Button, Collapse, Group,Loader,Text } from "@mantine/core";
|
||||
import { useDisclosure } from "@mantine/hooks";
|
||||
import React from "react";
|
||||
import SyntaxHighlighter from "react-syntax-highlighter";
|
||||
import LanguageCorner from "./LanguageCorner";
|
||||
import { okaidia } from "react-syntax-highlighter/dist/esm/styles/prism";
|
||||
import { IconCheck, IconChevronDown, IconFileDiff, IconLoader } from "@tabler/icons-react";
|
||||
import { observer } from "mobx-react-lite";
|
||||
import { useMst } from "@/views/stores/RootStore";
|
||||
import { keyframes,css } from "@emotion/react";
|
||||
|
||||
interface StepProps {
|
||||
language: string;
|
||||
children: string;
|
||||
done:boolean;
|
||||
}
|
||||
|
||||
const Step = observer((props:StepProps) => {
|
||||
const { chat } = useMst();
|
||||
const {language,children,done} = props;
|
||||
const [opened, { toggle }] = useDisclosure(false);
|
||||
|
||||
// extract first line with # as button label
|
||||
const lines = children.split('\n');
|
||||
const title = lines.length>0&&lines[0].indexOf('#')>=0?lines[0].split('#')[1]:'';
|
||||
const contents = lines.slice(1,lines.length-1);
|
||||
|
||||
const spin = keyframes`
|
||||
0% { transform: rotate(0deg); }
|
||||
100% { transform: rotate(360deg); }
|
||||
`;
|
||||
|
||||
return <Accordion
|
||||
variant="contained"
|
||||
chevronPosition="right"
|
||||
sx={{
|
||||
marginTop: 5,
|
||||
borderRadius: 5,
|
||||
backgroundColor: 'var(--vscode-menu-background)',
|
||||
}}
|
||||
styles={{
|
||||
item: {
|
||||
borderColor: 'var(--vscode-menu-border)',
|
||||
backgroundColor: 'var(--vscode-menu-background)',
|
||||
'&[data-active]': {
|
||||
backgroundColor: 'var(--vscode-menu-background)',
|
||||
}
|
||||
},
|
||||
control: {
|
||||
height: 30,
|
||||
borderRadius: 3,
|
||||
backgroundColor: 'var(--vscode-menu-background)',
|
||||
'&[aria-expanded="true"]': {
|
||||
borderBottomLeftRadius: 0,
|
||||
borderBottomRightRadius: 0,
|
||||
},
|
||||
'&:hover': {
|
||||
backgroundColor: 'var(--vscode-menu-background)',
|
||||
},
|
||||
paddingLeft: '0.5rem',
|
||||
paddingRight: '0.5rem',
|
||||
fontFamily: 'var(--vscode-editor-font-familyy)',
|
||||
fontSize: 'var(--vscode-editor-font-size)',
|
||||
},
|
||||
chevron: {
|
||||
color: 'var(--vscode-menu-foreground)',
|
||||
},
|
||||
icon: {
|
||||
color: 'var(--vscode-menu-foreground)',
|
||||
},
|
||||
label: {
|
||||
color: 'var(--vscode-menu-foreground)',
|
||||
},
|
||||
panel: {
|
||||
color: 'var(--vscode-menu-foreground)',
|
||||
backgroundColor: 'var(--vscode-menu-background)',
|
||||
},
|
||||
content: {
|
||||
borderRadius: 3,
|
||||
backgroundColor: 'var(--vscode-menu-background)',
|
||||
padding:'0.5rem'
|
||||
}
|
||||
}}
|
||||
>
|
||||
<Accordion.Item value={title} mah='200'>
|
||||
<Accordion.Control icon={
|
||||
done
|
||||
?<IconCheck size="1.125rem"/>
|
||||
:<Loader size="xs" color="#ED6A45" speed={1} />
|
||||
}
|
||||
>
|
||||
<Text truncate='end' w={chat.chatPanelWidth-100}>{title}</Text>
|
||||
</Accordion.Control>
|
||||
<Accordion.Panel>
|
||||
<SyntaxHighlighter {...props}
|
||||
language="markdown"
|
||||
style={okaidia}
|
||||
PreTag="div">
|
||||
{children}
|
||||
</SyntaxHighlighter>
|
||||
</Accordion.Panel>
|
||||
</Accordion.Item>
|
||||
</Accordion>;
|
||||
});
|
||||
|
||||
export default Step;
|
@ -1,299 +0,0 @@
|
||||
import { Button, Anchor, Stack, Group, Box, createStyles } from "@mantine/core";
|
||||
import React, { useEffect, useState } from "react";
|
||||
import ReactMarkdown from "react-markdown";
|
||||
import rehypeRaw from "rehype-raw";
|
||||
import { Prism as SyntaxHighlighter } from 'react-syntax-highlighter';
|
||||
import { okaidia } from "react-syntax-highlighter/dist/esm/styles/prism";
|
||||
import CodeButtons from "./CodeButtons";
|
||||
import Step from "./Step";
|
||||
import LanguageCorner from "./LanguageCorner";
|
||||
import { observer } from "mobx-react-lite";
|
||||
import { useMst } from "@/views/stores/RootStore";
|
||||
import { Message } from "@/views/stores/ChatStore";
|
||||
import messageUtil from '@/util/MessageUtil';
|
||||
import {fromMarkdown} from 'mdast-util-from-markdown';
|
||||
import {visit} from 'unist-util-visit';
|
||||
import ChatMark from "@/views/components/ChatMark";
|
||||
import { useSetState } from "@mantine/hooks";
|
||||
import { toMarkdown } from "mdast-util-to-markdown";
|
||||
|
||||
const useStyles = createStyles((theme) => ({
|
||||
link:{
|
||||
'&:hover':{
|
||||
color:theme.colors.merico[6]
|
||||
}
|
||||
}
|
||||
}));
|
||||
interface MessageMarkdownProps extends React.ComponentProps<typeof ReactMarkdown> {
|
||||
children: string,
|
||||
className: string,
|
||||
messageDone?: boolean,
|
||||
temp?: boolean
|
||||
}
|
||||
|
||||
type Step = {
|
||||
index: number,
|
||||
content: string;
|
||||
endsWithTripleBacktick: boolean;
|
||||
};
|
||||
|
||||
function parseMetaData(string) {
|
||||
const regexp = /((?<k1>(?!=)\S+)=((?<v1>(["'`])(.*?)\5)|(?<v2>\S+)))|(?<k2>\S+)/g;
|
||||
const io = (string ?? '').matchAll(regexp);
|
||||
|
||||
return new Map(
|
||||
[...io]
|
||||
.map((item) => item?.groups)
|
||||
.map(({ k1, k2, v1, v2 }) => [k1 ?? k2, v1 ?? v2]),
|
||||
);
|
||||
}
|
||||
|
||||
const MessageMarkdown = observer((props: MessageMarkdownProps) => {
|
||||
const { children,temp=false,messageDone } = props;
|
||||
const { chat } = useMst();
|
||||
const [steps, setSteps] = useState<Step[]>([]);
|
||||
const tree = fromMarkdown(children);
|
||||
const codes = tree.children.filter(node => node.type === 'code');
|
||||
const lastNode = tree.children[tree.children.length-1];
|
||||
const [chatmarkValues,setChatmarkValues] = useSetState({});
|
||||
const {classes} = useStyles();
|
||||
|
||||
const handleExplain = (value: string | undefined) => {
|
||||
console.log(value);
|
||||
switch (value) {
|
||||
case "#ask_code":
|
||||
chat.addMessages([
|
||||
Message.create({
|
||||
type: 'user',
|
||||
message: 'Explain /ask-code'
|
||||
}),
|
||||
Message.create({
|
||||
type: 'bot',
|
||||
message: `***/ask-code***
|
||||
|
||||
Ask anything about your codebase and get answers from our AI agent.
|
||||
|
||||
DevChat intelligently navigates your codebase using GPT-4. It automatically selects and analyzes up to ten most relevant source files to answer your question, all at an approximate cost of $0.4 USD. Stay tuned — we're soon integrating the more cost-efficient LLama 2 - 70B model.
|
||||
|
||||
Sample questions:
|
||||
- Why does the lead time for changes sometimes show as null?
|
||||
- How is store.findAllAccounts implemented?
|
||||
- The recursive retriever currently drops any TextNodes and only queries the IndexNodes. It's a bug. How can we fix it?
|
||||
`
|
||||
}),
|
||||
]);
|
||||
break;
|
||||
case '#code':
|
||||
chat.addMessages([
|
||||
Message.create({
|
||||
type: 'user',
|
||||
message: 'Explain /code'
|
||||
}),
|
||||
Message.create({
|
||||
type: 'bot',
|
||||
message: `***/code***
|
||||
|
||||
Use this DevChat workflow to request code writing. Please input your specific requirements and supply the appropriate context for implementation. You can select the relevant code or files and right-click to "Add to DevChat". If you find the context is still insufficient, you can enhance my understanding of your code by providing class/function definitions of the selected code. To do this, click the "+" button for the selected code and choose "symbol definitions". Please note, it may take a few seconds for this information to appear in DevChat.
|
||||
`
|
||||
}),
|
||||
]);
|
||||
break;
|
||||
case '#commit_message':
|
||||
chat.addMessages([
|
||||
Message.create({
|
||||
type: 'user',
|
||||
message: 'Explain /commit_message'
|
||||
}),
|
||||
Message.create({
|
||||
type: 'bot',
|
||||
message: `***/commit_message***
|
||||
|
||||
Use this DevChat workflow to request a commit message. Generally, you don't need to type anything else, but please give me the output of \`git diff\`. Of course, you don't need to manually execute the command and copy & paste its output. Simply click the "+" button and select \`git diff —cached\` to include only the staged changes, or \`git diff HEAD\` to include all changes.
|
||||
`
|
||||
}),
|
||||
]);
|
||||
break;
|
||||
case '#release_note':
|
||||
chat.addMessages([
|
||||
Message.create({
|
||||
type: 'user',
|
||||
message: 'Explain /release_note'
|
||||
}),
|
||||
Message.create({
|
||||
type: 'bot',
|
||||
message: `***/release_note***
|
||||
|
||||
Generate a professionally written and formatted release note in markdown with this workflow. I just need some basic information about the commits for the release. Add this to the context by clicking the "+" button and selecting \`git_log_releasenote\`. If the scope of commits differs from the default command, you can also select \`<custom command>\` and input a command line such as \`git log 579398b^..HEAD --pretty=format:"%h - %B"\` to include the commit 579398b (inclusive) up to the latest.
|
||||
`
|
||||
}),
|
||||
]);
|
||||
break;
|
||||
case "#settings":
|
||||
messageUtil.sendMessage({ command: 'doCommand', content: ['workbench.action.openSettings', 'DevChat'] });
|
||||
break;
|
||||
}
|
||||
chat.goScrollBottom();
|
||||
};
|
||||
const handleButton = (value: string | number | readonly string[] | undefined) => {
|
||||
switch (value) {
|
||||
case "settings": messageUtil.sendMessage({ command: 'doCommand', content: ['workbench.action.openSettings', 'DevChat'] }); break;
|
||||
case "setting_openai_key": messageUtil.sendMessage({ command: 'doCommand', content: ['DevChat.AccessKey.OpenAI'] }); break;
|
||||
case "setting_devchat_key": messageUtil.sendMessage({ command: 'doCommand', content: ['DevChat.AccessKey.DevChat'] }); break;
|
||||
}
|
||||
};
|
||||
|
||||
useEffect(()=>{
|
||||
let previousNode:any = null;
|
||||
let chatmarkCount = 0;
|
||||
visit(tree, function (node) {
|
||||
if (node.type === 'code') {
|
||||
// set meta data as props
|
||||
const metaData = parseMetaData(node.meta);
|
||||
let props = {...metaData};
|
||||
if(node.lang ==='chatmark' || node.lang ==='ChatMark'){
|
||||
props['index'] = chatmarkCount;
|
||||
} else if ((node.lang === 'yaml' || node.lang === 'YAML') && previousNode && previousNode.type === 'code' && previousNode.lang === 'chatmark') {
|
||||
setChatmarkValues({[`chatmark-${previousNode.data.hProperties.index}`]:node.value});
|
||||
}
|
||||
node.data={
|
||||
hProperties:{
|
||||
...props
|
||||
}
|
||||
};
|
||||
// record node and count data for next loop
|
||||
previousNode = node;
|
||||
if(node.lang ==='chatmark' || node.lang ==='ChatMark'){
|
||||
chatmarkCount++;
|
||||
}
|
||||
}
|
||||
});
|
||||
},[children]);
|
||||
|
||||
return <ReactMarkdown
|
||||
{...props}
|
||||
remarkPlugins={[()=> (tree) =>{
|
||||
let stepCount = 1;
|
||||
let chatmarkCount = 0;
|
||||
visit(tree, function (node) {
|
||||
if (node.type === 'code') {
|
||||
// set meta data as props
|
||||
const metaData = parseMetaData(node.meta);
|
||||
let props = {...metaData};
|
||||
if(node.lang ==='step' || node.lang ==='Step'){
|
||||
props['index'] = stepCount;
|
||||
} else if(node.lang ==='chatmark' || node.lang ==='ChatMark'){
|
||||
props['id'] = `chatmark-${chatmarkCount}`;
|
||||
props['index'] = chatmarkCount;
|
||||
}
|
||||
node.data={
|
||||
hProperties:{
|
||||
...props
|
||||
}
|
||||
};
|
||||
// record node and count data for next loop
|
||||
if(node.lang ==='chatmark' || node.lang ==='ChatMark'){
|
||||
chatmarkCount++;
|
||||
}
|
||||
if(node.lang ==='step' || node.lang ==='Step'){
|
||||
stepCount++;
|
||||
}
|
||||
}
|
||||
});
|
||||
}]}
|
||||
|
||||
rehypePlugins={[rehypeRaw]}
|
||||
components={{
|
||||
code({ node, inline, className, children, index, ...props }) {
|
||||
|
||||
const match = /language-(\w+)/.exec(className || '');
|
||||
const value = String(children).replace(/\n$/, '');
|
||||
let lanugage = match && match[1];
|
||||
if (!lanugage) {
|
||||
lanugage = "unknow";
|
||||
}
|
||||
|
||||
let wrapLongLines = false;
|
||||
if (lanugage === 'markdown' || lanugage === 'text') {
|
||||
wrapLongLines = true;
|
||||
}
|
||||
|
||||
if (lanugage === 'step' || lanugage === 'Step') {
|
||||
let done = Number(index) < codes.length? true : lastNode.type !== 'code';
|
||||
return <Step language={lanugage} done={temp?done:true}>{value}</Step>;
|
||||
}
|
||||
|
||||
if (lanugage === 'chatmark' || lanugage === 'ChatMark') {
|
||||
const chatmarkValue = chatmarkValues[`chatmark-${index}`];
|
||||
return <ChatMark value={chatmarkValue} messageDone={messageDone}>{value}</ChatMark>;
|
||||
}
|
||||
|
||||
return !inline && lanugage ? (
|
||||
<div style={{ position: 'relative' }}>
|
||||
<LanguageCorner language={lanugage} />
|
||||
<CodeButtons language={lanugage} code={value} />
|
||||
<SyntaxHighlighter {...props}
|
||||
language={lanugage}
|
||||
customStyle={{ padding: '3em 1em 1em 2em' }}
|
||||
style={okaidia}
|
||||
wrapLongLines={wrapLongLines}
|
||||
PreTag="div">
|
||||
{value}
|
||||
</SyntaxHighlighter>
|
||||
</div >
|
||||
) : (
|
||||
<code {...props} className={className}>
|
||||
{children}
|
||||
</code>
|
||||
);
|
||||
},
|
||||
button({ node, className, children, value, ...props }) {
|
||||
return (
|
||||
<Button
|
||||
size="compact-xs"
|
||||
sx={{
|
||||
backgroundColor:"#ED6A45",
|
||||
fontFamily: 'var(--vscode-editor-font-familyy)',
|
||||
fontSize: 'var(--vscode-editor-font-size)',
|
||||
color:"#fff",
|
||||
"&:hover":{
|
||||
backgroundColor:"#ED6A45",
|
||||
opacity: 0.8,
|
||||
},
|
||||
"&:focus":{
|
||||
backgroundColor:"#ED6A45",
|
||||
opacity: 0.8,
|
||||
}
|
||||
}}
|
||||
onClick={() => {
|
||||
handleButton(value);
|
||||
}}>
|
||||
{children}
|
||||
</Button>
|
||||
);
|
||||
},
|
||||
a({ node, className, children, href, ...props }) {
|
||||
const customAnchors = ["#code",
|
||||
"#commit_message",
|
||||
"#release_note",
|
||||
"#ask_code",
|
||||
"#extension",
|
||||
"#settings"].filter((item) => item === href);
|
||||
return customAnchors.length > 0
|
||||
? <Anchor
|
||||
className={classes.link}
|
||||
href={href} onClick={() => {
|
||||
handleExplain(href);
|
||||
}}>
|
||||
{children}
|
||||
</Anchor>
|
||||
: <a {...props} href={href} className={className}>
|
||||
{children}
|
||||
</a>;
|
||||
}
|
||||
}
|
||||
}>
|
||||
{children}
|
||||
</ReactMarkdown >;
|
||||
});
|
||||
|
||||
export default MessageMarkdown;
|
@ -1,43 +0,0 @@
|
||||
|
||||
import * as React from 'react';
|
||||
import { Button } from '@mantine/core';
|
||||
import { IconRotateDot } from '@tabler/icons-react';
|
||||
import { observer } from "mobx-react-lite";
|
||||
import { useMst } from "@/views/stores/RootStore";
|
||||
|
||||
|
||||
const RegenerationButton = observer(() => {
|
||||
const { chat } = useMst();
|
||||
return (<Button
|
||||
size='xs'
|
||||
sx={{
|
||||
backgroundColor:"#ED6A45",
|
||||
fontFamily: 'var(--vscode-editor-font-familyy)',
|
||||
fontSize: 'var(--vscode-editor-font-size)',
|
||||
color:"#fff",
|
||||
"&:hover":{
|
||||
backgroundColor:"#ED6A45",
|
||||
opacity: 0.8,
|
||||
},
|
||||
"&:focus":{
|
||||
backgroundColor:"#ED6A45",
|
||||
opacity: 0.8,
|
||||
}
|
||||
}}
|
||||
styles={{
|
||||
icon: {
|
||||
color:"#fff",
|
||||
},
|
||||
label: {
|
||||
fontSize: 'var(--vscode-editor-font-size)',
|
||||
color:"#fff",
|
||||
}
|
||||
}}
|
||||
leftIcon={<IconRotateDot color='var(--vscode-button-foreground)' />}
|
||||
onClick={() => chat.reGenerating()}
|
||||
variant="white" >
|
||||
Regeneration
|
||||
</Button >);
|
||||
});
|
||||
|
||||
export default RegenerationButton;
|
@ -1,49 +0,0 @@
|
||||
import * as React from 'react';
|
||||
import { Button } from '@mantine/core';
|
||||
import { IconPlayerStop } from '@tabler/icons-react';
|
||||
import messageUtil from '@/util/MessageUtil';
|
||||
import { observer } from "mobx-react-lite";
|
||||
import { useMst } from "@/views/stores/RootStore";
|
||||
|
||||
|
||||
const StopButton = observer(() => {
|
||||
const { chat } = useMst();
|
||||
return (
|
||||
<Button
|
||||
size="xs"
|
||||
sx={{
|
||||
backgroundColor:"#ED6A45",
|
||||
fontFamily: 'var(--vscode-editor-font-familyy)',
|
||||
fontSize: 'var(--vscode-editor-font-size)',
|
||||
color:"#fff",
|
||||
"&:hover":{
|
||||
backgroundColor:"#ED6A45",
|
||||
opacity: 0.8,
|
||||
},
|
||||
"&:focus":{
|
||||
backgroundColor:"#ED6A45",
|
||||
opacity: 0.8,
|
||||
}
|
||||
}}
|
||||
styles={{
|
||||
icon: {
|
||||
color:"#fff",
|
||||
},
|
||||
label: {
|
||||
fontSize: 'var(--vscode-editor-font-size)',
|
||||
color:"#fff",
|
||||
}
|
||||
}}
|
||||
leftIcon={<IconPlayerStop color='var(--vscode-button-foreground)' />}
|
||||
onClick={() => {
|
||||
chat.stopGenerating(false, '', chat.currentMessage);
|
||||
messageUtil.sendMessage({
|
||||
command: 'stopDevChat'
|
||||
});
|
||||
}}
|
||||
variant="white">
|
||||
Stop generating
|
||||
</Button>);
|
||||
});
|
||||
|
||||
export default StopButton;
|
@ -1,240 +0,0 @@
|
||||
import * as React from "react";
|
||||
import { useEffect, useRef } from "react";
|
||||
import {
|
||||
ActionIcon,
|
||||
Alert,
|
||||
Box,
|
||||
Button,
|
||||
Center,
|
||||
Stack,
|
||||
} from "@mantine/core";
|
||||
import { ScrollArea } from "@mantine/core";
|
||||
import { useElementSize, useResizeObserver, useTimeout, useViewportSize } from "@mantine/hooks";
|
||||
import messageUtil from "@/util/MessageUtil";
|
||||
import StopButton from "@/views/components/StopButton";
|
||||
import RegenerationButton from "@/views/components/RegenerationButton";
|
||||
import { observer } from "mobx-react-lite";
|
||||
import { useMst } from "@/views/stores/RootStore";
|
||||
import { Message } from "@/views/stores/ChatStore";
|
||||
|
||||
import InputMessage from "@/views/components/InputMessage";
|
||||
import MessageList from "@/views/components/MessageList";
|
||||
import {
|
||||
IconCircleArrowDownFilled,
|
||||
IconExternalLink,
|
||||
} from "@tabler/icons-react";
|
||||
|
||||
const chatPanel = observer(() => {
|
||||
const { input, chat } = useMst();
|
||||
|
||||
const [chatContainerRef, chatContainerRect] = useResizeObserver();
|
||||
const scrollViewport = useRef<HTMLDivElement>(null);
|
||||
const { height } = useViewportSize();
|
||||
const { ref:inputAreatRef, height:inputAreaHeight } = useElementSize();
|
||||
|
||||
|
||||
const chatPanelWidth = chatContainerRect.width;
|
||||
|
||||
const scrollToBottom = () =>
|
||||
scrollViewport?.current?.scrollTo({
|
||||
top: scrollViewport.current.scrollHeight,
|
||||
behavior: "smooth",
|
||||
});
|
||||
|
||||
const getSettings = () => {
|
||||
messageUtil.sendMessage({
|
||||
command: "getSetting",
|
||||
key1: "devchat",
|
||||
key2: "defaultModel",
|
||||
});
|
||||
};
|
||||
|
||||
const getFeatureToggles = () => {
|
||||
messageUtil.sendMessage({
|
||||
command: "featureToggles",
|
||||
});
|
||||
};
|
||||
|
||||
const timer = useTimeout(() => {
|
||||
if (chat.isBottom) {
|
||||
scrollToBottom();
|
||||
}
|
||||
}, 1000);
|
||||
|
||||
const onScrollPositionChange = ({ x, y }) => {
|
||||
const sh = scrollViewport.current?.scrollHeight || 0;
|
||||
const vh = scrollViewport.current?.clientHeight || 0;
|
||||
const gap = sh - vh - y;
|
||||
const isBottom = sh < vh ? true : gap < 100;
|
||||
const isTop = y === 0;
|
||||
// console.log(`sh:${sh},vh:${vh},x:${x},y:${y},gap:${gap}`);
|
||||
if (isBottom) {
|
||||
chat.onMessagesBottom();
|
||||
} else if (isTop) {
|
||||
chat.onMessagesTop();
|
||||
if (!chat.isLastPage) {
|
||||
//TODO: Data loading flickers and has poor performance, so I temporarily disabled the loading logic.
|
||||
// dispatch(fetchHistoryMessages({ pageIndex: pageIndex + 1 }));
|
||||
}
|
||||
} else {
|
||||
chat.onMessagesMiddle();
|
||||
}
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
getSettings();
|
||||
getFeatureToggles();
|
||||
chat.fetchHistoryMessages({ pageIndex: 0 }).then();
|
||||
messageUtil.registerHandler('reloadMessage',(message:any)=>{
|
||||
chat.reloadMessage(message);
|
||||
});
|
||||
messageUtil.registerHandler(
|
||||
"receiveMessagePartial",
|
||||
(message: { text: string }) => {
|
||||
chat.startResponsing(message.text);
|
||||
timer.start();
|
||||
}
|
||||
);
|
||||
messageUtil.registerHandler(
|
||||
"receiveMessage",
|
||||
(message: { text: string; isError: boolean; hash }) => {
|
||||
chat.stopGenerating(true, message.hash, message.text);
|
||||
if (message.isError) {
|
||||
chat.happendError(message.text);
|
||||
}
|
||||
}
|
||||
);
|
||||
messageUtil.registerHandler(
|
||||
"systemMessage",
|
||||
(message: { text: string }) => {
|
||||
const messageItem = Message.create({
|
||||
type: "system",
|
||||
message: message.text,
|
||||
});
|
||||
chat.newMessage(messageItem);
|
||||
// start generating
|
||||
chat.startSystemMessage();
|
||||
// Clear the input field
|
||||
input.setValue("");
|
||||
input.clearContexts();
|
||||
}
|
||||
);
|
||||
messageUtil.registerHandler("getSetting", (message: { value: string }) => {
|
||||
chat.changeChatModel(message.value);
|
||||
});
|
||||
messageUtil.registerHandler(
|
||||
"featureToggles",
|
||||
(message: { features: object }) => {
|
||||
// chat.changeChatModel(message.value);
|
||||
chat.updateFeatures(message.features);
|
||||
}
|
||||
);
|
||||
|
||||
timer.start();
|
||||
return () => {
|
||||
timer.clear();
|
||||
};
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
scrollToBottom();
|
||||
}, [chat.scrollBottom]);
|
||||
|
||||
useEffect(() => {
|
||||
chat.updateChatPanelWidth(chatPanelWidth);
|
||||
},[chatPanelWidth]);
|
||||
|
||||
return (
|
||||
<Stack
|
||||
ref={chatContainerRef}
|
||||
miw={300}
|
||||
spacing={0}
|
||||
sx={{
|
||||
height:'100%',
|
||||
background: "var(--vscode-sideBar-background)",
|
||||
color: "var(--vscode-editor-foreground)",
|
||||
}}
|
||||
>
|
||||
<ScrollArea
|
||||
sx={{
|
||||
height: height - inputAreaHeight - 40,
|
||||
margin: 0
|
||||
}}
|
||||
onScrollPositionChange={onScrollPositionChange}
|
||||
viewportRef={scrollViewport}
|
||||
>
|
||||
<MessageList />
|
||||
{chat.errorMessage && (
|
||||
<Box sx={{
|
||||
width: chatPanelWidth - 20,
|
||||
margin:'0 10px 40px 10px'
|
||||
}}>
|
||||
<Alert
|
||||
styles={{
|
||||
message: {
|
||||
width: chatPanelWidth - 50,
|
||||
whiteSpace: 'break-spaces',
|
||||
overflowWrap: 'break-word',
|
||||
fontSize: "var(--vscode-editor-font-size)"
|
||||
},
|
||||
}}
|
||||
color="gray"
|
||||
variant="filled"
|
||||
>
|
||||
{chat.errorMessage}
|
||||
</Alert>
|
||||
{chat.errorMessage.search("Insufficient balance") > -1 && (
|
||||
<Button
|
||||
size="xs"
|
||||
component="a"
|
||||
href={chat.rechargeSite}
|
||||
mt={5}
|
||||
variant="outline"
|
||||
leftIcon={<IconExternalLink size="0.9rem" />}
|
||||
>
|
||||
Open official website to recharge.
|
||||
</Button>
|
||||
)}
|
||||
</Box>
|
||||
)}
|
||||
{!chat.isBottom && (
|
||||
<ActionIcon
|
||||
onClick={() => {
|
||||
scrollToBottom();
|
||||
}}
|
||||
title="Bottom"
|
||||
variant="transparent"
|
||||
sx={{ position: "absolute", bottom: 5, right: 16, zIndex: 2 }}
|
||||
>
|
||||
<IconCircleArrowDownFilled size="1.125rem" />
|
||||
</ActionIcon>
|
||||
)}
|
||||
{chat.generating && (
|
||||
<Center sx={{ position: "absolute", bottom: 5, zIndex: 1,width:'100%' }}>
|
||||
<StopButton />
|
||||
</Center>
|
||||
)}
|
||||
{chat.errorMessage && (
|
||||
<Center sx={{ position: "absolute", bottom: 5, zIndex: 1,width:'100%' }}>
|
||||
<RegenerationButton />
|
||||
</Center>
|
||||
)}
|
||||
</ScrollArea>
|
||||
<Box
|
||||
ref={inputAreatRef}
|
||||
sx={{
|
||||
position:"absolute",
|
||||
bottom:0,
|
||||
width:chatPanelWidth>300||chatPanelWidth===0?"100%":chatPanelWidth,
|
||||
background: "var(--vscode-sideBar-background)",
|
||||
boxShadow: "0 0 10px 0 var(--vscode-widget-shadow)",
|
||||
borderTop:'1px solid #ced4da',
|
||||
}}
|
||||
>
|
||||
<InputMessage />
|
||||
</Box>
|
||||
</Stack>
|
||||
);
|
||||
});
|
||||
|
||||
export default chatPanel;
|
@ -1,401 +0,0 @@
|
||||
import { types, flow, Instance } from "mobx-state-tree";
|
||||
import messageUtil from '@/util/MessageUtil';
|
||||
import { ChatContext } from '@/views/stores/InputStore';
|
||||
import { features } from "process";
|
||||
import { Slice } from "@tiptap/pm/model";
|
||||
import yaml from 'js-yaml';
|
||||
|
||||
interface Context {
|
||||
content: string;
|
||||
role: string;
|
||||
}
|
||||
|
||||
interface Entry {
|
||||
hash: string;
|
||||
type: string,
|
||||
user: string;
|
||||
date: string;
|
||||
request: string;
|
||||
response: string;
|
||||
context: Context[];
|
||||
}
|
||||
|
||||
interface LoadHistoryMessage {
|
||||
command: string;
|
||||
entries: Entry[];
|
||||
}
|
||||
|
||||
export const fetchHistoryMessages = async (params) => {
|
||||
const { pageIndex } = params;
|
||||
return new Promise<{ pageIndex: number, entries: Entry[] }>((resolve, reject) => {
|
||||
try {
|
||||
messageUtil.sendMessage({ command: 'historyMessages', page: pageIndex });
|
||||
messageUtil.registerHandler('loadHistoryMessages', (message: LoadHistoryMessage) => {
|
||||
resolve({
|
||||
pageIndex: pageIndex,
|
||||
entries: message.entries
|
||||
});
|
||||
});
|
||||
} catch (e) {
|
||||
reject(e);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
interface DevChatInstalledMessage {
|
||||
command: string;
|
||||
result: boolean;
|
||||
}
|
||||
|
||||
|
||||
export const deleteMessage = async (messageHash: string) => {
|
||||
return new Promise<{ hash: string }>((resolve, reject) => {
|
||||
try {
|
||||
messageUtil.sendMessage({ command: 'deleteChatMessage', hash: messageHash });
|
||||
messageUtil.registerHandler('deletedChatMessage', (message) => {
|
||||
resolve({
|
||||
hash: message.hash
|
||||
});
|
||||
});
|
||||
} catch (e) {
|
||||
reject(e);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
export const Message = types.model({
|
||||
index: types.maybe(types.number),
|
||||
hash: types.maybe(types.string),
|
||||
type: types.enumeration(['user', 'bot', 'system']),
|
||||
message: types.string,
|
||||
contexts: types.maybe(types.array(ChatContext)),
|
||||
confirm: types.maybe(types.boolean)
|
||||
});
|
||||
|
||||
export const ChatStore = types.model('Chat', {
|
||||
generating: false,
|
||||
responsed: false,
|
||||
currentMessage: '',
|
||||
hasDone: false,
|
||||
errorMessage: '',
|
||||
messages: types.array(Message),
|
||||
pageIndex: 0,
|
||||
isLastPage: false,
|
||||
isBottom: true,
|
||||
isTop: false,
|
||||
scrollBottom: 0,
|
||||
chatModel: 'GPT-3.5',
|
||||
chatPanelWidth: 300,
|
||||
disabled: false,
|
||||
rechargeSite: 'https://web.devchat.ai/pricing/',
|
||||
features: types.optional(types.frozen(), {})
|
||||
})
|
||||
.actions(self => {
|
||||
|
||||
const goScrollBottom = () => {
|
||||
self.scrollBottom++;
|
||||
};
|
||||
|
||||
const lastNonEmptyHash = () => {
|
||||
let lastNonEmptyHash;
|
||||
for (let i = self.messages.length - 1; i >= 0; i--) {
|
||||
if (self.messages[i].hash) {
|
||||
lastNonEmptyHash = self.messages[i].hash;
|
||||
break;
|
||||
}
|
||||
}
|
||||
return lastNonEmptyHash === 'message' ? null : lastNonEmptyHash;
|
||||
};
|
||||
|
||||
// Process and send the message to the extension
|
||||
const contextInfo = chatContexts => chatContexts.map((item, index: number) => {
|
||||
const { file, path, content, command } = item;
|
||||
return {
|
||||
file,
|
||||
context: {
|
||||
path: path,
|
||||
command: command,
|
||||
content: content,
|
||||
}
|
||||
};
|
||||
});
|
||||
|
||||
const helpMessage = (originalMessage = false) => {
|
||||
|
||||
let helps = `
|
||||
Do you want to write some code or have a question about the project? Simply right-click on your chosen files or code snippets and add them to DevChat. Feel free to ask me anything or let me help you with coding.
|
||||
|
||||
Don't forget to check out the "+" button on the left of the input to add more context. To see a list of workflows you can run in the context, just type "/". Happy prompting!
|
||||
|
||||
To get started, here are some of the things that I can do for you:
|
||||
|
||||
[/code: write code based on your prompt](#code)
|
||||
|
||||
[/commit_message: compose a commit message based on your code changes](#commit_message)
|
||||
|
||||
[/release_note: draft a release note based on your latest commits](#release_note)
|
||||
|
||||
${self.features['ask-code'] ? '[/ask-code: ask anything about your codebase and get answers from our AI agent](#ask_code)' : ''}
|
||||
|
||||
You can configure DevChat from [Settings](#settings).`;
|
||||
|
||||
self.messages.push(
|
||||
Message.create({
|
||||
type: 'user',
|
||||
message: originalMessage ? "How do I use DevChat?" : '/help'
|
||||
}));
|
||||
self.messages.push(
|
||||
Message.create({
|
||||
type: 'bot',
|
||||
message: helps
|
||||
}));
|
||||
// goto bottom
|
||||
goScrollBottom();
|
||||
};
|
||||
|
||||
const startGenerating = (text: string, chatContexts) => {
|
||||
self.generating = true;
|
||||
self.responsed = false;
|
||||
self.hasDone = false;
|
||||
self.errorMessage = '';
|
||||
self.currentMessage = '';
|
||||
messageUtil.sendMessage({
|
||||
command: 'sendMessage',
|
||||
text: text,
|
||||
contextInfo: contextInfo(chatContexts),
|
||||
parent_hash: lastNonEmptyHash()
|
||||
});
|
||||
};
|
||||
|
||||
const sendLastUserMessage = () => {
|
||||
const lastUserMessage = self.messages[self.messages.length - 2];
|
||||
const lastBotMessage = self.messages[self.messages.length - 1];
|
||||
if (lastUserMessage && lastUserMessage.type === 'user') {
|
||||
lastBotMessage.confirm = false;
|
||||
startGenerating(lastUserMessage.message, lastUserMessage.contexts);
|
||||
}
|
||||
self.disabled = false;
|
||||
};
|
||||
|
||||
const cancelDevchatAsk = () => {
|
||||
const lastBotMessage = self.messages[self.messages.length - 1];
|
||||
if (lastBotMessage && lastBotMessage.type === 'bot') {
|
||||
lastBotMessage.confirm = false;
|
||||
lastBotMessage.message = 'You\'ve cancelled the question. Please let me know if you have any other questions or if there\'s anything else I can assist with.';
|
||||
}
|
||||
self.disabled = false;
|
||||
};
|
||||
|
||||
const commonMessage = (text: string, chatContexts) => {
|
||||
self.messages.push({
|
||||
type: 'user',
|
||||
message: text,
|
||||
contexts: chatContexts
|
||||
});
|
||||
self.messages.push({
|
||||
type: 'bot',
|
||||
message: ''
|
||||
});
|
||||
// start generating
|
||||
startGenerating(text, chatContexts);
|
||||
// goto bottom
|
||||
goScrollBottom();
|
||||
};
|
||||
|
||||
const userInput = (values:any) => {
|
||||
const inputStr = `
|
||||
\`\`\`yaml type=chatmark-values
|
||||
${yaml.dump(values)}
|
||||
\`\`\`
|
||||
`;
|
||||
self.currentMessage = self.currentMessage + inputStr;
|
||||
messageUtil.sendMessage({
|
||||
command: 'userInput',
|
||||
text: inputStr
|
||||
});
|
||||
// goto bottom
|
||||
goScrollBottom();
|
||||
};
|
||||
|
||||
return {
|
||||
helpMessage,
|
||||
sendLastUserMessage,
|
||||
cancelDevchatAsk,
|
||||
goScrollBottom,
|
||||
startGenerating,
|
||||
commonMessage,
|
||||
userInput,
|
||||
devchatAsk : flow(function* (userMessage, chatContexts) {
|
||||
self.messages.push({
|
||||
type: 'user',
|
||||
contexts: chatContexts,
|
||||
message: userMessage
|
||||
});
|
||||
const isInstalled = true;
|
||||
|
||||
if (isInstalled){
|
||||
// self.disabled = true;
|
||||
// self.errorMessage = '';
|
||||
// self.messages.push({
|
||||
// type: 'bot',
|
||||
// message: '',
|
||||
// confirm: true
|
||||
// });
|
||||
self.messages.push({
|
||||
type: 'bot',
|
||||
message: ''
|
||||
});
|
||||
startGenerating(userMessage, chatContexts);
|
||||
}
|
||||
|
||||
// goto bottom
|
||||
goScrollBottom();
|
||||
}),
|
||||
updateChatPanelWidth: (width: number) => {
|
||||
self.chatPanelWidth = width;
|
||||
},
|
||||
changeChatModel: (chatModel: string) => {
|
||||
self.chatModel = chatModel;
|
||||
},
|
||||
updateFeatures: (features: any) => {
|
||||
self.features = features;
|
||||
},
|
||||
startSystemMessage: () => {
|
||||
self.generating = true;
|
||||
self.responsed = false;
|
||||
self.hasDone = false;
|
||||
self.errorMessage = '';
|
||||
self.currentMessage = '';
|
||||
},
|
||||
reGenerating: () => {
|
||||
self.generating = true;
|
||||
self.responsed = false;
|
||||
self.hasDone = false;
|
||||
self.errorMessage = '';
|
||||
self.currentMessage = '';
|
||||
messageUtil.sendMessage({
|
||||
command: 'regeneration'
|
||||
});
|
||||
},
|
||||
stopGenerating: (hasDone: boolean, hash: string = '', message: string = '') => {
|
||||
self.generating = false;
|
||||
self.responsed = false;
|
||||
self.hasDone = hasDone;
|
||||
const messagesLength = self.messages.length;
|
||||
if (hasDone) {
|
||||
if (messagesLength > 1) {
|
||||
self.messages[messagesLength - 2].hash = hash;
|
||||
self.messages[messagesLength - 1].hash = hash;
|
||||
} else if (messagesLength > 0) {
|
||||
self.messages[messagesLength - 1].hash = hash;
|
||||
}
|
||||
} else {
|
||||
self.messages[messagesLength - 1].message = message;
|
||||
}
|
||||
},
|
||||
startResponsing: (message: string) => {
|
||||
self.responsed = true;
|
||||
self.currentMessage = message;
|
||||
},
|
||||
newMessage: (message: IMessage) => {
|
||||
self.messages.push(message);
|
||||
},
|
||||
addMessages: (messages: IMessage[]) => {
|
||||
self.messages.push(...messages);
|
||||
},
|
||||
updateLastMessage: (message: string) => {
|
||||
if (self.messages.length > 0) {
|
||||
self.messages[self.messages.length - 1].message = message;
|
||||
}
|
||||
},
|
||||
shiftMessage: () => {
|
||||
self.messages.splice(0, 1);
|
||||
},
|
||||
popMessage: () => {
|
||||
self.messages.pop();
|
||||
},
|
||||
clearMessages: () => {
|
||||
self.messages.length = 0;
|
||||
},
|
||||
happendError: (errorMessage: string) => {
|
||||
self.errorMessage = errorMessage;
|
||||
},
|
||||
onMessagesTop: () => {
|
||||
self.isTop = true;
|
||||
self.isBottom = false;
|
||||
},
|
||||
onMessagesBottom: () => {
|
||||
self.isTop = false;
|
||||
self.isBottom = true;
|
||||
},
|
||||
onMessagesMiddle: () => {
|
||||
self.isTop = false;
|
||||
self.isBottom = false;
|
||||
},
|
||||
reloadMessage:({entries,pageIndex})=>{
|
||||
if (entries.length > 0) {
|
||||
self.pageIndex = pageIndex;
|
||||
const messages = entries
|
||||
.map((entry, index) => {
|
||||
const { hash, user, date, request, response, context } = entry;
|
||||
const chatContexts = context?.map(({ content }) => {
|
||||
return JSON.parse(content);
|
||||
});
|
||||
return [
|
||||
{ type: 'user', message: request, contexts: chatContexts, date: date, hash: hash },
|
||||
{ type: 'bot', message: response, date: date, hash: hash },
|
||||
];
|
||||
})
|
||||
.flat();
|
||||
if (self.pageIndex === 0) {
|
||||
self.messages = messages;
|
||||
} else if (self.pageIndex > 0) {
|
||||
self.messages.concat(...messages);
|
||||
}
|
||||
} else {
|
||||
self.isLastPage = true;
|
||||
if (self.messages.length === 0) {
|
||||
helpMessage(true);
|
||||
}
|
||||
}
|
||||
},
|
||||
fetchHistoryMessages: flow(function* (params: { pageIndex: number }) {
|
||||
const { pageIndex, entries } = yield fetchHistoryMessages(params);
|
||||
if (entries.length > 0) {
|
||||
self.pageIndex = pageIndex;
|
||||
const messages = entries
|
||||
.map((entry, index) => {
|
||||
const { hash, user, date, request, response, context } = entry;
|
||||
const chatContexts = context?.map(({ content }) => {
|
||||
return JSON.parse(content);
|
||||
});
|
||||
return [
|
||||
{ type: 'user', message: request, contexts: chatContexts, date: date, hash: hash },
|
||||
{ type: 'bot', message: response, date: date, hash: hash },
|
||||
];
|
||||
})
|
||||
.flat();
|
||||
if (self.pageIndex === 0) {
|
||||
self.messages.push(...messages);
|
||||
} else if (self.pageIndex > 0) {
|
||||
self.messages.concat(...messages);
|
||||
}
|
||||
} else {
|
||||
self.isLastPage = true;
|
||||
if (self.messages.length === 0) {
|
||||
helpMessage(true);
|
||||
}
|
||||
}
|
||||
}),
|
||||
deleteMessage: flow(function* (messageHash: string) {
|
||||
const { hash } = yield deleteMessage(messageHash);
|
||||
const index = self.messages.findIndex((item: any) => item.hash === hash);
|
||||
if (index > -1) {
|
||||
self.messages.splice(index);
|
||||
}
|
||||
})
|
||||
};
|
||||
});
|
||||
|
||||
|
||||
export type IMessage = Instance<typeof Message>;
|
@ -1,133 +0,0 @@
|
||||
import { types, flow, Instance } from "mobx-state-tree";
|
||||
import messageUtil from '@/util/MessageUtil';
|
||||
import { IMessage } from '@/views/stores/ChatStore';
|
||||
|
||||
interface Item {
|
||||
name: string;
|
||||
pattern: string;
|
||||
description: string;
|
||||
}
|
||||
|
||||
const regContextMenus = async () => {
|
||||
return new Promise<Item[]>((resolve, reject) => {
|
||||
try {
|
||||
messageUtil.sendMessage({ command: 'regContextList' });
|
||||
messageUtil.registerHandler('regContextList', (message: { result: Item[] }) => {
|
||||
resolve(message.result);
|
||||
});
|
||||
} catch (e) {
|
||||
reject(e);
|
||||
}
|
||||
});
|
||||
};
|
||||
const regModelMenus = async () => {
|
||||
return new Promise<String[]>((resolve, reject) => {
|
||||
try {
|
||||
messageUtil.sendMessage({ command: 'regModelList' });
|
||||
messageUtil.registerHandler('regModelList', (message: {result: String[]} ) => {
|
||||
resolve(message.result);
|
||||
});
|
||||
} catch (e) {
|
||||
reject(e);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
export const ChatContext = types.model({
|
||||
file: types.maybe(types.string),
|
||||
path: types.maybe(types.string),
|
||||
command: types.maybe(types.string),
|
||||
content: types.string
|
||||
});
|
||||
|
||||
export const MenuItem = types.model({
|
||||
icon: types.maybe(types.string),
|
||||
name: types.string,
|
||||
pattern: types.maybe(types.string),
|
||||
description: types.string
|
||||
});
|
||||
|
||||
export const InputStore = types
|
||||
.model("Input", {
|
||||
value: "",
|
||||
contexts: types.array(ChatContext),
|
||||
menuType: "contexts",
|
||||
menuOpend: false,
|
||||
currentMenuIndex: 0,
|
||||
commandMenus: types.array(MenuItem),
|
||||
contextMenus: types.array(MenuItem),
|
||||
modelMenus: types.array(types.string)
|
||||
}).
|
||||
actions(self => ({
|
||||
setValue(value: string) {
|
||||
self.value = value;
|
||||
},
|
||||
removeContext(index: number) {
|
||||
self.contexts.splice(index, 1);
|
||||
},
|
||||
clearContexts() {
|
||||
self.contexts.clear();
|
||||
},
|
||||
setContexts(contexts: IChatContext[]) {
|
||||
self.contexts.clear();
|
||||
contexts?.forEach(context => {
|
||||
self.contexts.push({ ...context });
|
||||
});
|
||||
},
|
||||
newContext(context: IChatContext) {
|
||||
self.contexts.push(context);
|
||||
},
|
||||
openMenu(menuType: string) {
|
||||
self.menuOpend = true;
|
||||
self.menuType = menuType;
|
||||
},
|
||||
closeMenu() {
|
||||
self.menuOpend = false;
|
||||
self.menuType = '';
|
||||
},
|
||||
setCurrentMenuIndex(index: number) {
|
||||
self.currentMenuIndex = index;
|
||||
},
|
||||
updateCommands(items) {
|
||||
self.commandMenus.clear();
|
||||
self.commandMenus.push(...items);
|
||||
self.commandMenus.push({ name: 'help', description: 'View the DevChat documentation.', pattern: 'help' });
|
||||
},
|
||||
fetchContextMenus: flow(function* () {
|
||||
try {
|
||||
const items = yield regContextMenus();
|
||||
self.contextMenus.push(...items);
|
||||
} catch (error) {
|
||||
console.error("Failed to fetch context menus", error);
|
||||
}
|
||||
}),
|
||||
fetchModelMenus: flow(function* () {
|
||||
try {
|
||||
const models = yield regModelMenus();
|
||||
self.modelMenus.push(...models);
|
||||
} catch (error) {
|
||||
console.error("Failed to fetch context menus", error);
|
||||
}
|
||||
}),
|
||||
fetchCommandMenus: flow(function* () {
|
||||
const regCommandMenus = async () => {
|
||||
return new Promise<Item[]>((resolve, reject) => {
|
||||
try {
|
||||
messageUtil.sendMessage({ command: 'regCommandList' });
|
||||
} catch (e) {
|
||||
reject(e);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
try {
|
||||
yield regCommandMenus();
|
||||
} catch (error) {
|
||||
console.error("Failed to fetch command menus", error);
|
||||
}
|
||||
}),
|
||||
}));
|
||||
|
||||
|
||||
export type IInputStore = Instance<typeof InputStore>;
|
||||
export type IChatContext = Instance<typeof ChatContext>;
|
@ -1,27 +0,0 @@
|
||||
import { Instance, onSnapshot, types } from "mobx-state-tree";
|
||||
import { createContext, useContext } from "react";
|
||||
import { InputStore } from "@/views/stores/InputStore";
|
||||
import { ChatStore } from "@/views/stores/ChatStore";
|
||||
|
||||
const RootStore = types.model("Root", {
|
||||
input: InputStore,
|
||||
chat: ChatStore
|
||||
});
|
||||
|
||||
export const rootStore = RootStore.create({
|
||||
input: InputStore.create(),
|
||||
chat: ChatStore.create()
|
||||
});;
|
||||
|
||||
export type RootInstance = Instance<typeof RootStore>;
|
||||
const RootStoreContext = createContext<null | RootInstance>(null);
|
||||
|
||||
export const Provider = RootStoreContext.Provider;
|
||||
|
||||
export function useMst() {
|
||||
const store = useContext(RootStoreContext);
|
||||
if (store === null) {
|
||||
throw new Error("Store cannot be null, please add a context provider");
|
||||
}
|
||||
return store;
|
||||
}
|
@ -1,21 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Welcome to DevChat</title>
|
||||
<style>
|
||||
body {
|
||||
font-family: Arial, sans-serif;
|
||||
text-align: center;
|
||||
padding: 50px;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<h1>Welcome to DevChat!</h1>
|
||||
<p>Together, let's craft amazing code and documentation!</p>
|
||||
<p>To start, kindly open a Git or SVN repository folder and let's chat!</p>
|
||||
</body>
|
||||
</html>
|
@ -3,10 +3,6 @@
|
||||
"use strict";
|
||||
|
||||
const path = require("path");
|
||||
const HtmlWebpackPlugin = require("html-webpack-plugin");
|
||||
const { DefinePlugin } = require("webpack");
|
||||
const CopyWebpackPlugin = require("copy-webpack-plugin");
|
||||
const { CleanWebpackPlugin } = require("clean-webpack-plugin");
|
||||
|
||||
//@ts-check
|
||||
/** @typedef {import('webpack').Configuration} WebpackConfig **/
|
||||
@ -64,113 +60,4 @@ const extensionConfig = {
|
||||
],
|
||||
};
|
||||
|
||||
/** @type WebpackConfig */
|
||||
const webviewConfig = {
|
||||
name: "webview",
|
||||
target: "web",
|
||||
mode: "development",
|
||||
|
||||
entry: "./src/index.tsx",
|
||||
output: {
|
||||
path: path.resolve(__dirname, "dist"),
|
||||
filename: "index.js",
|
||||
publicPath: "/",
|
||||
},
|
||||
resolve: {
|
||||
extensions: [".ts", ".tsx", ".js", ".json"],
|
||||
alias: {
|
||||
"@": path.resolve(__dirname, "src/"),
|
||||
},
|
||||
},
|
||||
module: {
|
||||
rules: [
|
||||
{
|
||||
test: /\.tsx?$/,
|
||||
exclude: /node_modules/,
|
||||
use: [
|
||||
{
|
||||
loader: "babel-loader",
|
||||
options: {
|
||||
presets: [
|
||||
"@babel/preset-env",
|
||||
"@babel/preset-react",
|
||||
"@babel/preset-typescript",
|
||||
],
|
||||
},
|
||||
},
|
||||
{
|
||||
loader: "ts-loader",
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
test: /\.jsx?$/,
|
||||
exclude: /node_modules/,
|
||||
use: [
|
||||
{
|
||||
loader: "babel-loader",
|
||||
options: {
|
||||
presets: [["@babel/preset-env", "@babel/preset-react"]],
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
test: /\.css$/i,
|
||||
use: [
|
||||
{
|
||||
loader: "style-loader",
|
||||
},
|
||||
{
|
||||
loader: "css-loader",
|
||||
options: {
|
||||
modules: {
|
||||
localIdentName: "[name]__[local]___[hash:base64:5]",
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
include: /views/,
|
||||
},
|
||||
{
|
||||
test: /\.json$/i,
|
||||
use: "json-loader",
|
||||
type: "asset/source",
|
||||
},
|
||||
{
|
||||
test: /\.(png|jpg|jpeg|gif|svg)$/, // 匹配文件类型
|
||||
use: [
|
||||
{
|
||||
loader: "file-loader", // 使用file-loader
|
||||
options: {
|
||||
name: "[name].[ext]", // 输出文件的名称和扩展名
|
||||
outputPath: "assets/", // 输出文件的路径
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
devtool: "source-map",
|
||||
infrastructureLogging: {
|
||||
level: "log",
|
||||
},
|
||||
plugins: [
|
||||
// generate an HTML file that includes the extension's JavaScript file
|
||||
new HtmlWebpackPlugin({
|
||||
template: path.resolve(__dirname, "src", "index.html"),
|
||||
filename: "index.html",
|
||||
chunks: ["index"],
|
||||
}),
|
||||
new HtmlWebpackPlugin({
|
||||
template: path.resolve(__dirname, "src", "welcome.html"),
|
||||
filename: "welcome.html",
|
||||
chunks: ["welcome"],
|
||||
}),
|
||||
new DefinePlugin({
|
||||
"process.env.platform": JSON.stringify("vscode"),
|
||||
}),
|
||||
],
|
||||
};
|
||||
|
||||
module.exports = extensionConfig;
|
||||
|
@ -1,105 +0,0 @@
|
||||
//@ts-check
|
||||
"use strict";
|
||||
|
||||
const path = require("path");
|
||||
const HtmlWebpackPlugin = require("html-webpack-plugin");
|
||||
const { CleanWebpackPlugin } = require("clean-webpack-plugin");
|
||||
const webpack = require("webpack");
|
||||
|
||||
const webviewConfig = {
|
||||
name: "webview",
|
||||
target: "web",
|
||||
mode: "production",
|
||||
entry: "./src/index.tsx",
|
||||
output: {
|
||||
path: path.resolve(__dirname, "dist"),
|
||||
filename: "main.js",
|
||||
publicPath: "/",
|
||||
},
|
||||
resolve: {
|
||||
extensions: [".ts", ".tsx", ".js", ".json"],
|
||||
alias: {
|
||||
"@": path.resolve(__dirname, "src/"),
|
||||
},
|
||||
},
|
||||
module: {
|
||||
rules: [
|
||||
{
|
||||
test: /\.tsx?$/,
|
||||
exclude: /node_modules/,
|
||||
use: [
|
||||
{
|
||||
loader: "babel-loader",
|
||||
options: {
|
||||
presets: [
|
||||
"@babel/preset-env",
|
||||
"@babel/preset-react",
|
||||
"@babel/preset-typescript",
|
||||
],
|
||||
},
|
||||
},
|
||||
{
|
||||
loader: "ts-loader",
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
test: /\.jsx?$/,
|
||||
exclude: /node_modules/,
|
||||
use: [
|
||||
{
|
||||
loader: "babel-loader",
|
||||
options: {
|
||||
presets: [["@babel/preset-env", "@babel/preset-react"]],
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
test: /\.css$/i,
|
||||
use: [
|
||||
{
|
||||
loader: "style-loader",
|
||||
},
|
||||
{
|
||||
loader: "css-loader",
|
||||
options: {
|
||||
modules: {
|
||||
localIdentName: "[name]__[local]___[hash:base64:5]",
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
include: /views/,
|
||||
},
|
||||
{
|
||||
test: /\.json$/i,
|
||||
use: "json-loader",
|
||||
type: "asset/source",
|
||||
},
|
||||
{
|
||||
test: /\.(png|jpg|jpeg|gif|svg)$/, // 匹配文件类型
|
||||
type: "asset/inline",
|
||||
},
|
||||
],
|
||||
},
|
||||
devtool: false,
|
||||
infrastructureLogging: {
|
||||
level: "log",
|
||||
},
|
||||
plugins: [
|
||||
// generate an HTML file that includes the extension's JavaScript file
|
||||
new HtmlWebpackPlugin({
|
||||
template: path.resolve(__dirname, "src", "main.html"),
|
||||
filename: "main.html",
|
||||
chunks: ["main"],
|
||||
}),
|
||||
new CleanWebpackPlugin(),
|
||||
new webpack.ProgressPlugin(),
|
||||
new webpack.DefinePlugin({
|
||||
"process.env.platform": JSON.stringify("idea"),
|
||||
}),
|
||||
],
|
||||
};
|
||||
|
||||
module.exports = webviewConfig;
|
Loading…
x
Reference in New Issue
Block a user