Compare commits

..

No commits in common. "main" and "fix-webapp-address-syncing" have entirely different histories.

31 changed files with 947 additions and 993 deletions

5
.env
View File

@ -1,4 +1 @@
REACT_APP_IMAGE_BASE_URL=https://img2.baidu.com REACT_APP_IMAGE_BASE_URL=https://img2.baidu.com
REACT_APP_ASSISTANT_DISPLAY_NAME_EN=DevChat
REACT_APP_ASSISTANT_DISPLAY_NAME_ZH=DevChat
REACT_APP_LOGO_FILE=

View File

@ -1,4 +0,0 @@
REACT_APP_IMAGE_BASE_URL=https://img2.baidu.com
REACT_APP_ASSISTANT_DISPLAY_NAME_EN=
REACT_APP_ASSISTANT_DISPLAY_NAME_ZH=
REACT_APP_LOGO_FILE=

View File

@ -1,38 +0,0 @@
---
name: Bug report
about: Create a report to help us improve
title: ''
labels: ''
assignees: ''
---
**Describe the bug**
A clear and concise description of what the bug is.
**To Reproduce**
Steps to reproduce the behavior:
1. Go to '...'
2. Click on '....'
3. Scroll down to '....'
4. See error
**Expected behavior**
A clear and concise description of what you expected to happen.
**Screenshots**
If applicable, add screenshots to help explain your problem.
**Desktop (please complete the following information):**
- OS: [e.g. iOS]
- Browser [e.g. chrome, safari]
- Version [e.g. 22]
**Smartphone (please complete the following information):**
- Device: [e.g. iPhone6]
- OS: [e.g. iOS8.1]
- Browser [e.g. stock browser, safari]
- Version [e.g. 22]
**Additional context**
Add any other context about the problem here.

View File

@ -1,20 +0,0 @@
---
name: Feature request
about: Suggest an idea for this project
title: ''
labels: ''
assignees: ''
---
**Is your feature request related to a problem? Please describe.**
A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
**Describe the solution you'd like**
A clear and concise description of what you want to happen.
**Describe alternatives you've considered**
A clear and concise description of any alternative solutions or features you've considered.
**Additional context**
Add any other context or screenshots about the feature request here.

1
.gitignore vendored
View File

@ -22,4 +22,3 @@ dist-ssr
*.njsproj *.njsproj
*.sln *.sln
*.sw? *.sw?
avatar_devchat.svg

View File

@ -1,34 +0,0 @@
# devchat-gui
The unified webview UI of DevChat plugins.
## Parameterize packaging
### Step 1: Config the assistant names and logo file [Optional]
Custom assistant name and logo are supported, they can be configured in the `.env` file
~~~env
# Default to DevChat
REACT_APP_ASSISTANT_DISPLAY_NAME_EN=English name of the AI assistant
# Default to DevChat
REACT_APP_ASSISTANT_DISPLAY_NAME_ZH=Chinese name of the AI assistant
# Default to DevChat logo
REACT_APP_LOGO_FILE=/path/to/the/logo.svg
~~~
Notes:
1. The logo should be a `.svg` file;
2. Recommend size of the logo is `64x64`;
### Step 2: Build the UI for `VSCode` and `IntelliJ`
The `devchat-gui` repo is assumed to be a submodule of `devchat-vscode` and `devchat-intellij`. You need to build `devchat-gui` first before you build your plugins.
~~~bash
cd gui
yarn idea # for intellij
yarn vscode # for vscode
~~~

View File

@ -53,11 +53,11 @@
"lint": "eslint src --ext ts", "lint": "eslint src --ext ts",
"test": "mocha", "test": "mocha",
"build": "webpack --config webpack.config.js", "build": "webpack --config webpack.config.js",
"vscode": "node prebuild.js && webpack --config webpack.config.js && shx mv dist/* ../dist && git checkout -- src/views/components/MessageAvatar/avatar_devchat.svg", "vscode": "webpack --config webpack.config.js && mv dist/* ../dist",
"vscode:watch": "webpack --config webpack.config.js --watch", "vscode:watch": "webpack --config webpack.config.js --watch",
"dev": "webpack serve --config webpack.config.js --open", "dev": "webpack serve --config webpack.config.js --open",
"build:idea": "webpack --config webpack.idea.config.js", "build:idea": "webpack --config webpack.idea.config.js",
"idea": "node prebuild.js && webpack --config webpack.idea.config.js && shx mv dist/main.js dist/main.html ../src/main/resources/static && git checkout -- src/views/components/MessageAvatar/avatar_devchat.svg && echo '🎆done'" "idea": "webpack --config webpack.idea.config.js && mv dist/main.js dist/main.html ../src/main/resources/static && echo '🎆done'"
}, },
"devDependencies": { "devDependencies": {
"@babel/core": "^7.21.8", "@babel/core": "^7.21.8",
@ -96,7 +96,6 @@
"proxyquire": "^2.1.3", "proxyquire": "^2.1.3",
"react": "^18.2.0", "react": "^18.2.0",
"react-dom": "^18.2.0", "react-dom": "^18.2.0",
"shx": "^0.3.4",
"sinon": "^15.1.0", "sinon": "^15.1.0",
"style-loader": "^3.3.2", "style-loader": "^3.3.2",
"ts-jest": "^29.1.0", "ts-jest": "^29.1.0",

View File

@ -1,23 +0,0 @@
require('dotenv').config();
const fs = require('fs');
const path = require('path');
const logoPath = process.env.REACT_APP_LOGO_FILE;
if (!logoPath) {
console.warn('REACT_APP_LOGO_FILE is not defined in your environment variables');
process.exit(0);
}
if (!fs.existsSync(logoPath)) {
console.warn('Logo file does not exist.');
process.exit(0)
}
const destPath = path.join(__dirname, 'src/views/components/MessageAvatar/avatar_devchat.svg');
try {
fs.copyFileSync(logoPath, destPath)
fs.chmodSync(destPath, 0o644)
} catch(e) {
console.warn(`Failed to copy logo ${e}`)
}

View File

@ -1,15 +1,5 @@
import axios from "axios"; import axios from "axios";
import { v4 as uuidv4 } from 'uuid'; import { v4 as uuidv4 } from 'uuid';
import IDEServiceUtil from "./IDEServiceUtil";
interface EventData {
ide: string | undefined;
[key: string]: any; // For other potential properties
}
interface MessageData {
ide: string | undefined;
[key: string]: any; // For other potential properties
}
class APIUtil { class APIUtil {
private static instance: APIUtil; private static instance: APIUtil;
@ -17,7 +7,6 @@ class APIUtil {
private accessKey: string | undefined; private accessKey: string | undefined;
private webappUrl: string | undefined; private webappUrl: string | undefined;
private currentMessageId: string | undefined; private currentMessageId: string | undefined;
private extensionVersion: string | undefined;
constructor() { constructor() {
@ -30,22 +19,6 @@ class APIUtil {
} }
return APIUtil.instance; return APIUtil.instance;
} }
async getExtensionVersion(): Promise<string | undefined> {
if (this.extensionVersion) {
return this.extensionVersion;
}
try {
const version = await IDEServiceUtil.callService("get_extension_version", {});
this.extensionVersion = version || "unknown";
return this.extensionVersion;
} catch (err) {
console.error("Failed to get extension version:", err);
return "unknown";
}
}
async fetchWebappUrl() { async fetchWebappUrl() {
try { try {
const res = await axios.get( const res = await axios.get(
@ -85,74 +58,38 @@ class APIUtil {
}) })
} }
updateCurrentMessageId() { async createMessage(message: object) {
this.currentMessageId = `msg-${uuidv4()}`; this.currentMessageId = `msg-${uuidv4()}`;
return this.currentMessageId;
}
getCurrentMessageId() {
return this.currentMessageId;
}
async createMessage(message: MessageData, messageId?: string) {
// 如果 messageId 为空,则使用 uuid 生成新的 ID
var newMessageId = messageId || `msg-${uuidv4()}`;
newMessageId = newMessageId || this.currentMessageId || '';
try { try {
if (!this.webappUrl) this.webappUrl = await this.fetchWebappUrl(); if (!this.webappUrl) this.webappUrl = await this.fetchWebappUrl();
// 获取版本号并更新ide字段
const version = await this.getExtensionVersion() || "unknown";
message.ide = `${message.ide}[${version}]`;
const res = await axios.post( const res = await axios.post(
`${this.webappUrl}/api/v1/messages`, `${this.webappUrl}/api/v1/messages`,
{...message, message_id: newMessageId}, {...message, message_id: this.currentMessageId},
{ headers: { { headers: {
Authorization: `Bearer ${this.accessKey}`, Authorization: `Bearer ${this.accessKey}`,
'Content-Type': 'application/json', 'Content-Type': 'application/json',
}} }}
); )
console.log("Message created: ", res?.data); console.log("Message created: ", res?.data);
} catch(err) { } catch(err) {
console.error(err); console.error(err);
} }
} }
async createEvent(event: EventData, messageId?: string) {
// 如果 messageId 为空,则使用当前的 messageId
const idToUse = messageId || this.currentMessageId;
// 获取版本号
const version = await this.getExtensionVersion() || "unknow";
// 更新event.ide, 添加version信息。
// 原来值为vscode,修改后值为vscode[0.1.96]
event.ide = `${event.ide}[${version}]`;
const attemptCreate = async () => {
try {
if (!this.webappUrl) this.webappUrl = await this.fetchWebappUrl();
const res = await axios.post(
`${this.webappUrl}/api/v1/messages/${idToUse}/events`,
event,
{headers: {
Authorization: `Bearer ${this.accessKey}`,
'Content-Type': 'application/json',
}}
);
console.log("Event created: ", res?.data);
return true;
} catch(err) {
if (axios.isAxiosError(err) && err.response?.status === 404) {
return false;
}
console.error(err);
return true;
}
};
if (!(await attemptCreate())) { async createEvent(event: object) {
await new Promise(resolve => setTimeout(resolve, 2000)); try {
await attemptCreate(); if (!this.webappUrl) this.webappUrl = await this.fetchWebappUrl();
const res = await axios.post(
`${this.webappUrl}/api/v1/messages/${this.currentMessageId}/events`,
event,
{headers: {
Authorization: `Bearer ${this.accessKey}`,
'Content-Type': 'application/json',
}}
)
console.log("Event created: ", res?.data);
} catch(err) {
console.error(err);
} }
} }
@ -172,4 +109,4 @@ class APIUtil {
} }
export default APIUtil.getInstance(); export default APIUtil.getInstance();

View File

@ -1,73 +0,0 @@
import axios from "axios";
interface ServiceResponse {
result?: any;
error?: string;
}
class IDEServiceUtil {
private static instance: IDEServiceUtil;
private host = "http://localhost";
private port: number | undefined;
constructor() {
console.log("IDEServiceUtil ready");
}
public static getInstance(): IDEServiceUtil {
if (!IDEServiceUtil.instance) {
IDEServiceUtil.instance = new IDEServiceUtil();
}
return IDEServiceUtil.instance;
}
config(port: number) {
console.log("Recieved IDEService port: ", port);
this.port = port;
}
async getCurrentFileInfo() {
try {
if (!this.port) return undefined;
const res = await axios.get(`${this.host}:${this.port}/current_file_info`)
const info = res?.data?.result;
console.log("currentFileInfo: ", info);
return info;
} catch(err) {
console.error(err);
}
}
async callService(serviceName: string, data: Record<string, any>): Promise<any> {
console.log("callService: ", serviceName, data);
try {
const url = `${this.host}:${this.port}/${serviceName}`;
console.log("callService url: ", url);
// eslint-disable-next-line @typescript-eslint/naming-convention
const headers = { 'Content-Type': 'application/json' };
const response = await axios.post<ServiceResponse>(url, data, { headers });
console.log("callService response: ", response);
if (response.status !== 200) {
console.log(`Server error: ${response.status}`);
return undefined;
}
const responseData = response.data;
if (responseData.error) {
console.log(`Server returned error: ${responseData.error}`);
return undefined;
}
return responseData.result;
} catch (error) {
console.error('Error calling service:', error);
return undefined;
}
}
}
export default IDEServiceUtil.getInstance();

View File

@ -1,15 +1,28 @@
import IdeaBridge from "./ideaBridge";
class MessageUtil { class MessageUtil {
private static instance: MessageUtil; private static instance: MessageUtil;
handlers: { [x: string]: any }; handlers: { [x: string]: any };
ideApi: any; vscodeApi: any;
messageListener: any; messageListener: any;
hasInit: boolean;
constructor() { constructor() {
this.hasInit = false; this.handlers = {};
this.handlers = {}; this.messageListener = null;
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 { public static getInstance(): MessageUtil {
@ -19,43 +32,20 @@ class MessageUtil {
return MessageUtil.instance; return MessageUtil.instance;
} }
public init() {
if (this.hasInit) {
return;
}
this.hasInit = true;
this.handlers = {};
this.messageListener = null;
if (process.env.platform === "vscode") {
this.ideApi = window.acquireVsCodeApi();
} else if (process.env.platform === "idea") {
this.ideApi = window.acquireIdeaCodeApi();
}
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.");
}
}
// Register a message handler for a specific message type // Register a message handler for a specific message type
registerHandler(messageType: string, handler: any) { registerHandler(messageType: string, handler: any) {
this.init(); if (process.env.platform === "idea") {
if (!this.handlers[messageType]) { IdeaBridge.registerHandler(messageType, handler);
} else {
if (!this.handlers[messageType]) {
this.handlers[messageType] = []; this.handlers[messageType] = [];
}
this.handlers[messageType].push(handler);
} }
this.handlers[messageType].push(handler);
} }
// Unregister a message handler for a specific message type // Unregister a message handler for a specific message type
unregisterHandler(messageType: string | number, handler: any) { unregisterHandler(messageType: string | number, handler: any) {
this.init();
if (this.handlers[messageType]) { if (this.handlers[messageType]) {
this.handlers[messageType] = this.handlers[messageType].filter( this.handlers[messageType] = this.handlers[messageType].filter(
(h: any) => h !== handler (h: any) => h !== handler
@ -64,24 +54,22 @@ class MessageUtil {
} }
// Handle a received message // Handle a received message
handleMessage(message: { command: string | number } & Record<string, any>) { handleMessage(message: { command: string | number }) {
this.init();
if (!('command' in message)) {
throw new Error('Missing required field: command');
}
const handlers = this.handlers[message.command]; const handlers = this.handlers[message.command];
if (handlers) { if (handlers) {
handlers.forEach((handler: (arg0: { command: string | number } & Record<string, any>) => any) => handlers.forEach((handler: (arg0: { command: string | number }) => any) =>
handler(message) handler(message)
); );
} }
} }
// Send a message to the VSCode API // Send a message to the VSCode API
sendMessage(message: any) { sendMessage(message: any) {
this.init(); if (process.env.platform === "idea") {
this.ideApi.postMessage(message); IdeaBridge.sendMessage(message);
} else {
this.vscodeApi.postMessage(message);
}
} }
} }

View File

@ -1,5 +1,7 @@
## sendMessage ## sendMessage
- getUserAccessKey // 获取 access key
- doCommit // 提交代码
- updateSetting // 更新设置(目前只有更换模型) - updateSetting // 更新设置(目前只有更换模型)
- getSetting // 获取默认模型 - getSetting // 获取默认模型
- deleteChatMessage // 删除最近一条消息 - deleteChatMessage // 删除最近一条消息
@ -11,24 +13,31 @@
// 1. 打开设置 // 1. 打开设置
// 2. 启动 ask code 安装 // 2. 启动 ask code 安装
// 3. 设置 access key // 3. 设置 access key
- featureToggles // 判断有没有 ask code
- isDevChatInstalled // 判断 ask code 是否安装 - isDevChatInstalled // 判断 ask code 是否安装
- historyMessages // 页面的历史消息 - historyMessages // 页面的历史消息
- contextDetail // 获取 appendContext 响应之后,发送次请求获取文件的内容 - contextDetail // 获取 appendContext 响应之后,发送次请求获取文件的内容
- addContext // 点击 context 菜单(比如 git diff之后发送到消息
- code_file_apply // 代码应用到 editor替换 current file - code_file_apply // 代码应用到 editor替换 current file
- code_apply // 代码应用到 editor 光标位置 - code_apply // 代码应用到 editor 光标位置
- sendMessage // 发送消息 - sendMessage // 发送消息
- regeneration // 错误时重新生成 - regeneration // 错误时重新生成
- regContextList // git diff 之类的列表
- regModelList // model 列表
- regCommandList // 输入 / 之后出现的列表 - regCommandList // 输入 / 之后出现的列表
## registerHandler ## registerHandler
- getUserAccessKey // 获取 access key
- regCommandList // 获取 / 之后出现的列表 - regCommandList // 获取 / 之后出现的列表
- appendContext // 右键添加到 context 或者 context 菜单点击的响应 - appendContext // 右键添加到 context 或者 context 菜单点击的响应
- contextDetailResponse // 获取到的文件内容 - contextDetailResponse // 获取到的文件内容
- loadHistoryMessages // 与 historyMessages 对应 - loadHistoryMessages // 与 historyMessages 对应
- isDevChatInstalled // 与 isDevChatInstalled 对应 - isDevChatInstalled // 与 isDevChatInstalled 对应
- deletedChatMessage // 与 deleteChatMessage 对应 - deletedChatMessage // 与 deleteChatMessage 对应
- regContextList // 与 regContextList 对应
- regModelList // 与 regModelList
- receiveMessagePartial // 部分对话 - receiveMessagePartial // 部分对话
- receiveMessage // 对话 - receiveMessage // 对话
- systemMessage // 没用了 - systemMessage // 没用了

View File

@ -1,2 +0,0 @@
export const ASSISTANT_DISPLAY_NAME: string = process.env.REACT_APP_ASSISTANT_DISPLAY_NAME_EN || "DevChat"
export const ASSISTANT_DISPLAY_NAME_ZH: string = process.env.REACT_APP_ASSISTANT_DISPLAY_NAME_ZH || "DevChat"

614
src/util/ideaBridge.ts Normal file
View File

@ -0,0 +1,614 @@
const JStoIdea = {
sendMessage: (
message: string,
context: any = [],
parent: string = "",
model: 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,
model,
},
};
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));
},
newSrcFile: (language, content) => {
const params = {
action: "newSrcFile/request",
metadata: {
callback: "IdeaToJSMessage",
},
payload: {
language: language,
content: content,
},
};
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));
},
etcCommand: (command: any) => {
/**
*
* 1. workbench.action.openSettings
* 2. AskCodeIndexStart
* 3. AccessKey.OpenAI
* 4. DevChat.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 "DevChat.AccessKey.DevChat":
// 设置key
const setkeyparams = {
action: "showSettingDialog/request",
metadata: {
callback: "IdeaToJSMessage",
},
payload: {},
};
window.JSJavaBridge.callJava(JSON.stringify(setkeyparams));
break;
default:
break;
}
},
getTopicList: () => {
// 获取 topic 列表
const params = {
action: "listTopics/request",
metadata: {
callback: "IdeaToJSMessage",
},
payload: {},
};
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));
},
setNewTopic: () => {
const params = {
action: "loadConversations/request",
metadata: {
callback: "IdeaToJSMessage",
topicHash: "",
},
payload: {},
};
window.JSJavaBridge.callJava(JSON.stringify(params));
},
deleteTopic: (topicHash: string) => {
const params = {
action: "deleteTopic/request",
metadata: {
callback: "IdeaToJSMessage",
topicHash: topicHash,
},
payload: {
topicHash: topicHash,
},
};
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));
},
userInput: (message) => {
const params = {
action: "input/request",
metadata: {
callback: "IdeaToJSMessage",
},
payload: {
data: message?.text || "",
},
};
window.JSJavaBridge.callJava(JSON.stringify(params));
},
stopDevChat: () => {
const params = {
action: "stopGeneration/request",
metadata: {
callback: "IdeaToJSMessage",
},
payload: {},
};
window.JSJavaBridge.callJava(JSON.stringify(params));
},
regeneration: () => {
const params = {
action: "regeneration/request",
metadata: {
callback: "IdeaToJSMessage",
},
payload: {},
};
window.JSJavaBridge.callJava(JSON.stringify(params));
},
readConfig: () => {
// 获取完整的用户设置
const params = {
action: "getSetting/request",
metadata: {
callback: "IdeaToJSMessage",
},
payload: {},
};
window.JSJavaBridge.callJava(JSON.stringify(params));
},
saveConfig: (data) => {
const params = {
action: "updateSetting/request",
metadata: {
callback: "IdeaToJSMessage",
},
payload: data,
};
console.log("ready to call java: ", JSON.stringify(params));
window.JSJavaBridge.callJava(JSON.stringify(params));
},
};
class IdeaBridge {
private static instance: IdeaBridge;
handle: any = {};
constructor() {
this.handle = {};
// 注册全局的回调函数,用于接收来自 IDEA 的消息
window.IdeaToJSMessage = (res: any) => {
console.info("IdeaToJSMessage get res: ", res);
switch (res.action) {
case "updateSetting/response":
this.resviceUpdateSetting(res);
break;
case "codeDiffApply/response":
this.resviceCodeDiffApply(res);
break;
case "sendUserMessage/response":
this.resviceSendUserMessage(res);
break;
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;
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;
}
};
window.onInitializationFinish = () => {
// 初始化完成
JStoIdea.getCommandList();
};
}
resviceUpdateSetting(res) {
// 更新用户设置之后的回调
this.executeHandlers("updateSetting", res.payload);
}
resviceCodeDiffApply(res) {
this.executeHandlers("codeDiffApply", res.payload);
}
resviceSendUserMessage(res) {
this.executeHandlers("chatWithDevChat", {
command: "chatWithDevChat",
message: res.payload.message || "",
});
}
resviceDeleteMessage(res) {
const hash = res?.payload?.promptHash || "";
// this.handle.deletedChatMessage({
// hash,
// });
this.executeHandlers("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,
// });
this.executeHandlers("reloadMessage", {
entries: list.reverse(),
pageIndex: 0,
reset: list.length === 0,
});
}
resviceTopicDetail(res) {
// 用于重置后端全局的 topic id
if (res?.payload?.reset) {
// 重置后请求历史消息
JStoIdea.historyMessages({ page: 0 });
}
}
resviceTopicList(res) {
const list = res.payload.topics;
// this.handle.listTopics(list);
this.executeHandlers("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);
this.executeHandlers("contextDetailResponse", params);
}
resviceSettings(res) {
// 获取用户设置的回调
const setting = res?.payload || {};
this.executeHandlers("readConfig", {
value: setting,
});
}
resviceCommandList(res) {
this.executeHandlers("regCommandList", {
result: res.payload.commands,
});
}
resviceContextList(res) {
// 接受到的上下文列表
const result = res.payload.contexts.map((item) => ({
name: item.command,
pattern: item.command,
description: item.description,
}));
// this.handle.regContextList({ result });
this.executeHandlers("regContextList", {
result,
});
}
resviceModelList(response: any) {
// 接受到模型列表
this.executeHandlers("regModelList", {
result: response.payload.models,
});
}
resviceMessage(response: any) {
// 接受到消息
if (response.metadata?.isFinalChunk) {
// 结束对话
this.executeHandlers("receiveMessage", {
text: response.payload?.message || response.metadata?.error || "",
isError: response.metadata?.error.length > 0,
hash: response.payload?.promptHash || "",
});
} else {
this.executeHandlers("receiveMessagePartial", {
text: response?.payload?.message || "",
});
}
}
public static getInstance(): IdeaBridge {
if (!IdeaBridge.instance) {
IdeaBridge.instance = new IdeaBridge();
}
return IdeaBridge.instance;
}
registerHandler(messageType: string, handler: any) {
if (!this.handle[messageType]) {
this.handle[messageType] = [];
}
this.handle[messageType].push(handler);
}
executeHandlers(messageType: string, data: any) {
if (this.handle[messageType]) {
this.handle[messageType].forEach((handler) => {
handler(data);
});
}
}
sendMessage(message: any) {
// 根据 command 分发到不同的方法·
switch (message.command) {
// 发送消息
case "sendMessage":
JStoIdea.sendMessage(
message.text,
message.contextInfo,
message.parent_hash,
message.model
);
break;
// 重新生成消息,用于发送失败时再次发送
case "regeneration":
JStoIdea.regeneration();
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 "code_new_file":
JStoIdea.newSrcFile(message.language, message.content);
break;
case "doCommand":
JStoIdea.etcCommand(message);
break;
case "show_diff":
JStoIdea.viewDiff(message.content);
break;
case "doCommit":
JStoIdea.commit(message.content);
break;
case "getTopics":
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;
case "userInput":
JStoIdea.userInput(message);
break;
case "setNewTopic":
JStoIdea.setNewTopic();
break;
case "deleteTopic":
JStoIdea.deleteTopic(message.topicHash);
break;
case "stopDevChat":
JStoIdea.stopDevChat();
break;
case "readConfig":
JStoIdea.readConfig();
break;
case "writeConfig":
// 保存用户设置
JStoIdea.saveConfig(message.value);
break;
default:
break;
}
}
}
export default IdeaBridge.getInstance();

View File

@ -26,78 +26,14 @@ export default function App() {
}; };
const getConfig = () => { const getConfig = () => {
// 比较函数
const compare_func = (configs: any) => {
const [serverConfig, userConfig] = config.updateConfig(configs.server_config, configs.server_config_base, configs.user_config);
if (serverConfig) {
// save server config as base
MessageUtil.sendMessage({ command: "writeServerConfigBase", value: serverConfig });
}
const modelList = Object.keys(userConfig.models || {});
// 判断是否需要重设默认模型值
if (!userConfig.default_model || !modelList.includes(userConfig.default_model)) {
// 需要重新设置默认模型
if (serverConfig && serverConfig.default_model && modelList.includes(serverConfig.default_model)) {
// 优先使用服务器配置中的默认设置
userConfig.default_model = serverConfig.default_model;
} else {
// 使用 chat 类型的第一个模型
const chatModel = modelList.find(model => userConfig.models[model].category === "chat");
userConfig.default_model = chatModel || modelList[0] || "";
}
}
// set user config
config.setConfig(userConfig);
};
// 结果记录
let configs = {};
let configCount = 0;
// 处理函数
const handleConfig = (key: string, data: { value: any }) => {
console.log("-----> receive:", key);
configs[key] = {...data.value};
configCount++;
checkConfigs();
};
// 检查是否所有配置都已准备好
const checkConfigs = () => {
if (configCount === 3) {
compare_func(configs);
// 假设setReady是一个函数用于设置应用状态为准备就绪
setReady(true);
// update workflow list
config.updateWorkflowList();
}
};
// 注册处理函数
MessageUtil.registerHandler("readConfig", (data: { value: any }) => { MessageUtil.registerHandler("readConfig", (data: { value: any }) => {
console.log("readConfig registerHandler: ", data);
config.setConfig(data.value); config.setConfig(data.value);
APIUtil.config(config.getAPIBase(), config.getUserKey()); APIUtil.config(config.getAPIBase(), config.getUserKey())
config.refreshModelList();
setReady(true); setReady(true);
config.fetchServerConfig();
handleConfig("user_config", data);
}); });
MessageUtil.registerHandler("readServerConfigBase", (data: { value: any }) => handleConfig("server_config_base", data)); MessageUtil.sendMessage({ command: "readConfig", key: "" });
MessageUtil.registerHandler("readServerConfig", (data: { value: any }) => handleConfig("server_config", data));
MessageUtil.registerHandler(
"reloadConfig",
(data: { value: any }) => {
configs = {};
configCount = 0;
// 发送消息
MessageUtil.sendMessage({ command: "readConfig", key: "" });
MessageUtil.sendMessage({ command: "readServerConfigBase", key: "" });
});
MessageUtil.handleMessage({ command: "reloadConfig" });
}; };
useEffect(() => { useEffect(() => {

View File

@ -7,13 +7,10 @@ import {
Radio, Radio,
Textarea, Textarea,
createStyles, createStyles,
MultiSelect
} from "@mantine/core"; } from "@mantine/core";
import { useListState, useSetState } from "@mantine/hooks"; import { useListState, useSetState } from "@mantine/hooks";
import { useMst } from "@/views/stores/RootStore"; import { useMst } from "@/views/stores/RootStore";
import yaml from "js-yaml"; import yaml from "js-yaml";
import IDEServiceUtil from "@/util/IDEServiceUtil";
import APIUtil from "@/util/APIUtil";
const useStyles = createStyles((theme) => ({ const useStyles = createStyles((theme) => ({
container: { container: {
@ -59,7 +56,7 @@ interface IWdiget {
id: string; id: string;
value: string; value: string;
title?: string; title?: string;
type: "editor" | "checkbox" | "radio" | "button" | "text"| "multiSelect"; type: "editor" | "checkbox" | "radio" | "button" | "text";
submit?: string; submit?: string;
cancel?: string; cancel?: string;
} }
@ -78,7 +75,6 @@ const ChatMark = ({
const values = value ? yaml.load(value) : {}; const values = value ? yaml.load(value) : {};
const [disabled, setDisabled] = useState(messageDone || !!value); const [disabled, setDisabled] = useState(messageDone || !!value);
const [checkboxArray, setcheckboxArray] = useState<any>([]); const [checkboxArray, setcheckboxArray] = useState<any>([]);
const platform = process.env.platform === "idea" ? "intellij" : process.env.platform;
const handleSubmit = () => { const handleSubmit = () => {
let formData = {}; let formData = {};
@ -95,24 +91,12 @@ const ChatMark = ({
formData[widget.id] = widget.value; formData[widget.id] = widget.value;
}); });
chat.userInput(formData); chat.userInput(formData);
IDEServiceUtil.getCurrentFileInfo().then(info => APIUtil.createEvent({
name: "submit",
value: JSON.stringify(formData),
ide: platform,
language: info?.extension || info?.path?.split('.').pop()
}));
}; };
const handleCancel = () => { const handleCancel = () => {
chat.userInput({ chat.userInput({
form: "canceled", form: "canceled",
}); });
IDEServiceUtil.getCurrentFileInfo().then(info => APIUtil.createEvent({
name: "cancel",
ide: platform,
language: info?.extension || info?.path?.split('.').pop()
}));
}; };
const handleButtonClick = ({ event, index }) => { const handleButtonClick = ({ event, index }) => {
@ -168,23 +152,6 @@ const ChatMark = ({
widget["value"] = event.currentTarget.value; widget["value"] = event.currentTarget.value;
widgetsHandlers.setItem(index, widget); widgetsHandlers.setItem(index, widget);
}; };
const handleSelectChange = (values:string[],allValues:string[]) => {
widgetsHandlers.apply((item) => {
if (allValues.includes(item.id)) {
if(!values.length){
item.value='unchecked';
return item;
}
item.value = "unchecked";
values.forEach(el=>{
if(item.id==el){
item.value = "checked";
}
})
}
return item;
});
};
useEffect(() => { useEffect(() => {
const lines = children.split("\n"); const lines = children.split("\n");
@ -194,7 +161,6 @@ const ChatMark = ({
const textRegex = /^([^>].*)/; // Text widget const textRegex = /^([^>].*)/; // Text widget
const buttonRegex = /^>\s*\((.*?)\)\s*(.*)/; // Button widget const buttonRegex = /^>\s*\((.*?)\)\s*(.*)/; // Button widget
const checkboxRegex = /^>\s*\[([x ]*)\]\((.*?)\)\s*(.*)/; // Checkbox widget const checkboxRegex = /^>\s*\[([x ]*)\]\((.*?)\)\s*(.*)/; // Checkbox widget
const selectRegex = /^>\s*\{([x ]*)\}\((.*?)\)\s*(.*)/; //MultiSelect widget
const radioRegex = /^>\s*-\s*\((.*?)\)\s*(.*)/; // Radio button widget const radioRegex = /^>\s*-\s*\((.*?)\)\s*(.*)/; // Radio button widget
const editorRegex = /^>\s*\|\s*\((.*?)\)/; // Editor widget const editorRegex = /^>\s*\|\s*\((.*?)\)/; // Editor widget
const editorContentRegex = /^>\s*(.*)/; // Editor widget const editorContentRegex = /^>\s*(.*)/; // Editor widget
@ -236,40 +202,6 @@ const ChatMark = ({
? checkArrayTemp[checkArrayTemp.length - 1] ? checkArrayTemp[checkArrayTemp.length - 1]
: {}; : {};
if (prevIsCheckbox) {
currentCheckboxData.group.push({
id: id,
// 只记录初始化时的状态,后续状态变化不会更新
check: check,
});
} else {
currentCheckboxData = {
id: `select-all-${id}`,
allChecked: false,
indeterminate: false,
check: false,
group: [{ id: id, check: check }],
};
checkArrayTemp.push(currentCheckboxData);
}
}else if ((match = line.match(selectRegex))) {
const [status, id, title] = match.slice(1);
const check = value
? "unchecked"
: status === "x"
? "checked"
: "unchecked";
widgetsHandlers.append({
id,
title,
type: "multiSelect",
value: check,
});
setAutoForm(true);
let currentCheckboxData: any = prevIsCheckbox
? checkArrayTemp[checkArrayTemp.length - 1]
: {};
if (prevIsCheckbox) { if (prevIsCheckbox) {
currentCheckboxData.group.push({ currentCheckboxData.group.push({
id: id, id: id,
@ -357,9 +289,6 @@ const ChatMark = ({
let radioValuesTemp: any = []; let radioValuesTemp: any = [];
let wdigetsTemp: any = []; let wdigetsTemp: any = [];
let prevIsCheckbox = false; let prevIsCheckbox = false;
let groupTemp: any = [];
let valuesTemp: any = [];
let selectValues: any = [];
widgets.map((widget, index) => { widgets.map((widget, index) => {
if (widget.type === "text") { if (widget.type === "text") {
wdigetsTemp.push(<Text key={index}>{widget.value}</Text>); wdigetsTemp.push(<Text key={index}>{widget.value}</Text>);
@ -477,34 +406,6 @@ const ChatMark = ({
onChange={(event) => handleEditorChange({ event, index })} onChange={(event) => handleEditorChange({ event, index })}
/> />
); );
}else if(widget.type === "multiSelect"){
if(widget.value==="checked")selectValues.push(widget.id);
groupTemp.push({label:widget.title,value:widget.id});
valuesTemp.push(widget.id);
// if next widget is not radio, then end current group
const nextWidget =
index + 1 < widgets.length ? widgets[index + 1] : null;
if (!nextWidget || nextWidget.type !== "multiSelect") {
const multiSelect = ((data, allValues) => {
return (
<MultiSelect
disabled={disabled}
styles={{searchInput:{outline:'none !important'}}}
data={data}
disableSelectedItemFiltering
label=""
nothingFound="暂无数据"
placeholder="请选择您的task编号"
searchable
value={selectValues}
onChange={(values)=>handleSelectChange(values,allValues)}
/>);
})(groupTemp, valuesTemp);
groupTemp = [];
valuesTemp = [];
selectValues = [];
wdigetsTemp.push(multiSelect);
}
} }
prevIsCheckbox = widget.type === "checkbox"; prevIsCheckbox = widget.type === "checkbox";

View File

@ -15,7 +15,6 @@ import SvgAvatarDevChat from "../MessageAvatar/avatar_devchat.svg";
import messageUtil from "@/util/MessageUtil"; import messageUtil from "@/util/MessageUtil";
import { useRouter } from "@/views/router"; import { useRouter } from "@/views/router";
import { useMst } from "@/views/stores/RootStore"; import { useMst } from "@/views/stores/RootStore";
import { ASSISTANT_DISPLAY_NAME } from "@/util/constants";
const useStyles = createStyles((theme) => ({ const useStyles = createStyles((theme) => ({
logoName: { logoName: {
@ -26,7 +25,7 @@ const useStyles = createStyles((theme) => ({
export default function Head() { export default function Head() {
const router = useRouter(); const router = useRouter();
const { classes } = useStyles(); const { classes } = useStyles();
const { t, i18n } = useTranslation(); const { i18n } = useTranslation();
const { config } = useMst(); const { config } = useMst();
useEffect(() => { useEffect(() => {
@ -75,7 +74,7 @@ export default function Head() {
> >
<Avatar color="indigo" size={25} radius="xl" src={SvgAvatarDevChat} /> <Avatar color="indigo" size={25} radius="xl" src={SvgAvatarDevChat} />
<Text weight="bold" className={classes.logoName}> <Text weight="bold" className={classes.logoName}>
{t(ASSISTANT_DISPLAY_NAME)} DevChat
</Text> </Text>
</Flex> </Flex>
<Flex align="center" gap="xs" sx={{ paddingRight: 10 }}> <Flex align="center" gap="xs" sx={{ paddingRight: 10 }}>

View File

@ -17,7 +17,6 @@ import {
import { useDisclosure } from "@mantine/hooks"; import { useDisclosure } from "@mantine/hooks";
import messageUtil from "@/util/MessageUtil"; import messageUtil from "@/util/MessageUtil";
import dayjs from "dayjs"; import dayjs from "dayjs";
import { ASSISTANT_DISPLAY_NAME } from "@/util/constants";
export default function Topic({ styleName, disabled }) { export default function Topic({ styleName, disabled }) {
const [drawerOpened, { open: openDrawer, close: closeDrawer }] = const [drawerOpened, { open: openDrawer, close: closeDrawer }] =
@ -89,7 +88,7 @@ export default function Topic({ styleName, disabled }) {
position="bottom" position="bottom"
title={ title={
<Flex justify="space-between"> <Flex justify="space-between">
<Text>{ASSISTANT_DISPLAY_NAME} Topics</Text> <Text>DevChat Topics</Text>
<Flex> <Flex>
<ActionIcon onClick={refreshTopicList}> <ActionIcon onClick={refreshTopicList}>
<IconRefresh size="1rem" /> <IconRefresh size="1rem" />

View File

@ -32,7 +32,6 @@ import { useMst } from "@/views/stores/RootStore";
import { ChatContext } from "@/views/stores/InputStore"; import { ChatContext } from "@/views/stores/InputStore";
import { Trans, useTranslation } from "react-i18next"; import { Trans, useTranslation } from "react-i18next";
import getModelShowName from "@/util/getModelShowName"; import getModelShowName from "@/util/getModelShowName";
import { ASSISTANT_DISPLAY_NAME } from "@/util/constants";
const useStyles = createStyles(() => ({ const useStyles = createStyles(() => ({
actionIcon: { actionIcon: {
@ -64,6 +63,7 @@ const InputMessage = observer((props: any) => {
menuOpend, menuOpend,
menuType, menuType,
currentMenuIndex, currentMenuIndex,
contextMenus,
commandMenus commandMenus
} = input; } = input;
const { generating } = chat; const { generating } = chat;
@ -88,19 +88,7 @@ const InputMessage = observer((props: any) => {
input.setValue(value); input.setValue(value);
}; };
const resetCursor = () => {
if (inputRef.current) {
inputRef.current.selectionStart = 0;
inputRef.current.selectionEnd = 0;
inputRef.current.focus();
}
};
const handleSendClick = (event: React.MouseEvent<HTMLButtonElement>) => { const handleSendClick = (event: React.MouseEvent<HTMLButtonElement>) => {
if (generating || chat.disabled) {
return;
}
const inputValue = input.value; const inputValue = input.value;
if (inputValue) { if (inputValue) {
if (inputValue.trim() === "/help") { if (inputValue.trim() === "/help") {
@ -118,22 +106,20 @@ const InputMessage = observer((props: any) => {
} }
// Clear the input field // Clear the input field
input.setValue(""); input.setValue("");
setTimeout(resetCursor, 0);
input.clearContexts(); input.clearContexts();
// event.preventDefault(); // event.preventDefault();
} }
}; };
const handleKeyDown = (event: React.KeyboardEvent<HTMLTextAreaElement>) => { const handleContextClick = (contextName: string) => {
if (generating) { // Process and send the message to the extension
if (event.key === 'Enter' && messageUtil.sendMessage({
!event.shiftKey && command: "addContext",
!event.nativeEvent.isComposing) { selected: contextName,
event.preventDefault(); });
} };
return;
}
const handleKeyDown = (event: React.KeyboardEvent<HTMLTextAreaElement>) => {
if (menuOpend) { if (menuOpend) {
if (event.key === "Escape") { if (event.key === "Escape") {
input.closeMenu(); input.closeMenu();
@ -179,7 +165,6 @@ const InputMessage = observer((props: any) => {
!event.nativeEvent.isComposing !event.nativeEvent.isComposing
) { ) {
handleSendClick(event as any); handleSendClick(event as any);
event.preventDefault();
} }
} }
}; };
@ -188,8 +173,7 @@ const InputMessage = observer((props: any) => {
messageUtil.registerHandler( messageUtil.registerHandler(
"chatWithDevChat", "chatWithDevChat",
(message: { command: string; message: string }) => { (message: { command: string; message: string }) => {
input.setValue(message.message); chat.commonMessage(message.message, []);
handleSendClick(event as any);
} }
); );
messageUtil.registerHandler( messageUtil.registerHandler(
@ -418,7 +402,7 @@ const InputMessage = observer((props: any) => {
opened={drawerOpened} opened={drawerOpened}
onClose={closeDrawer} onClose={closeDrawer}
position="bottom" position="bottom"
title={`${ASSISTANT_DISPLAY_NAME} Contexts`} title="DevChat Contexts"
overlayProps={{ opacity: 0.5, blur: 4 }} overlayProps={{ opacity: 0.5, blur: 4 }}
closeButtonProps={{ children: <IconChevronDown size="1rem" /> }} closeButtonProps={{ children: <IconChevronDown size="1rem" /> }}
styles={{ styles={{
@ -448,7 +432,7 @@ const InputMessage = observer((props: any) => {
<Popover.Target> <Popover.Target>
<Textarea <Textarea
id="chat-textarea" id="chat-textarea"
// disabled={generating || chat.disabled} disabled={generating || chat.disabled}
value={input.value} value={input.value}
ref={inputRef} ref={inputRef}
onKeyDown={handleKeyDown} onKeyDown={handleKeyDown}
@ -463,7 +447,7 @@ const InputMessage = observer((props: any) => {
marginTop: 5, marginTop: 5,
marginBottom: 5, marginBottom: 5,
}} }}
placeholder={t("devchat.input_placeholder", {assistantName: t(ASSISTANT_DISPLAY_NAME)})} placeholder={t("Ask DevChat a question or type / for workflow")}
styles={{ styles={{
rightSection: { rightSection: {
alignItems: "flex-end", alignItems: "flex-end",

View File

@ -23,7 +23,6 @@ import { useTranslation } from "react-i18next";
import { useMst } from "@/views/stores/RootStore"; import { useMst } from "@/views/stores/RootStore";
import { IMessage } from "@/views/stores/ChatStore"; import { IMessage } from "@/views/stores/ChatStore";
import { IChatContext } from "@/views/stores/InputStore"; import { IChatContext } from "@/views/stores/InputStore";
import { ASSISTANT_DISPLAY_NAME } from "@/util/constants";
interface IProps { interface IProps {
item?: IMessage; item?: IMessage;
@ -61,7 +60,7 @@ const MessageAvatar = observer((props: IProps) => {
) : ( ) : (
<Avatar color="cyan" size={25} radius="xl" src={SvgAvatarUser} /> <Avatar color="cyan" size={25} radius="xl" src={SvgAvatarUser} />
)} )}
<Text weight="bold">{avatarType === "bot" ? t(ASSISTANT_DISPLAY_NAME) : t("User")}</Text> <Text weight="bold">{avatarType === "bot" ? "DevChat" : t("User")}</Text>
{avatarType === "user" ? ( {avatarType === "user" ? (
<Flex <Flex
gap="xs" gap="xs"

View File

@ -1,11 +1,10 @@
import { Tooltip, ActionIcon, CopyButton, Flex } from "@mantine/core"; import { Tooltip, ActionIcon, CopyButton, Flex } from "@mantine/core";
import { IconCheck, IconGitCommit, IconFileDiff, IconEdit, IconColumnInsertRight, IconReplace, IconCopy,IconFile } from "@tabler/icons-react"; import { IconCheck, IconGitCommit, IconFileDiff, IconColumnInsertRight, IconReplace, IconCopy,IconFile } from "@tabler/icons-react";
import React, { useState } from "react"; import React, { useState } from "react";
import { useMst } from "@/views/stores/RootStore"; import { useMst } from "@/views/stores/RootStore";
import messageUtil from '@/util/MessageUtil'; import messageUtil from '@/util/MessageUtil';
import APIUtil from "@/util/APIUtil"; import APIUtil from "@/util/APIUtil";
import IDEServiceUtil from "@/util/IDEServiceUtil";
const IconButton = ({ label, color = 'gray', onClick, children }) => ( const IconButton = ({ label, color = 'gray', onClick, children }) => (
<Tooltip sx={{ padding: '3px', fontSize: 'var(--vscode-editor-font-size)' }} label={label} withArrow position="left" color="gray"> <Tooltip sx={{ padding: '3px', fontSize: 'var(--vscode-editor-font-size)' }} label={label} withArrow position="left" color="gray">
@ -15,16 +14,31 @@ const IconButton = ({ label, color = 'gray', onClick, children }) => (
</Tooltip> </Tooltip>
); );
const CodeCopyButton = ({ code, language, platform }) => { 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 }) => {
const {config} = useMst();
return ( return (
<CopyButton value={code} timeout={2000}> <CopyButton value={code} timeout={2000}>
{({ copied, copy }) => ( {({ copied, copy }) => (
<IconButton label={copied ? 'Copied' : 'Copy'} color={copied ? 'teal' : 'gray'} onClick={() => { <IconButton label={copied ? 'Copied' : 'Copy'} color={copied ? 'teal' : 'gray'} onClick={() => {
copy(); copy();
APIUtil.createEvent( APIUtil.createEvent({name: 'copy', value: 'copy'})
{name: 'copy', value: code, language: language, ide: platform},
APIUtil.getCurrentMessageId()
);
}}> }}>
{copied ? <IconCheck size="1rem" /> : <IconCopy size="1rem" />} {copied ? <IconCheck size="1rem" /> : <IconCopy size="1rem" />}
</IconButton> </IconButton>
@ -33,30 +47,16 @@ const CodeCopyButton = ({ code, language, platform }) => {
); );
}; };
const DiffButton = ({ code, language, platform }) => { const DiffButton = ({ code }) => {
const {config} = useMst();
const handleClick = () => { const handleClick = () => {
const e = 'show_diff'; const e = 'show_diff';
let selectedCode = code;
const selection = window.getSelection();
if (selection) {
selectedCode = selection.toString().trim();
}
// If no code is selected, use the entire code block
if (!selectedCode) {
selectedCode = code;
}
messageUtil.sendMessage({ messageUtil.sendMessage({
command: e, command: e,
content: selectedCode content: code
}); });
APIUtil.createEvent( APIUtil.createEvent({name: e, value: e})
{name: e, value: selectedCode, language: language, ide: platform},
APIUtil.getCurrentMessageId()
);
}; };
return ( return (
<IconButton label='View Diff' onClick={handleClick}> <IconButton label='View Diff' onClick={handleClick}>
<IconFileDiff size="1.125rem" /> <IconFileDiff size="1.125rem" />
@ -64,36 +64,15 @@ const DiffButton = ({ code, language, platform }) => {
); );
}; };
const EditApplyButton = ({ code, language, platform }) => { const CodeApplyButton = ({ code }) => {
const handleClick = () => { const {config} = useMst();
IDEServiceUtil.callService("diff_apply", {
filepath: "",
content: code,
autoedit: true
});
APIUtil.createEvent(
{name: "edit_apply", value: code, language: language, ide: platform},
APIUtil.getCurrentMessageId()
);
};
return (
<IconButton label='Apply Code' onClick={handleClick}>
<IconEdit size="1.125rem" />
</IconButton>
);
};
const CodeApplyButton = ({ code, language, platform }) => {
const handleClick = () => { const handleClick = () => {
const e = 'code_apply'; const e = 'code_apply';
messageUtil.sendMessage({ messageUtil.sendMessage({
command: e, command: e,
content: code content: code
}); });
APIUtil.createEvent( APIUtil.createEvent({name: e, value: e})
{name: e, value: code, language: language, ide: platform},
APIUtil.getCurrentMessageId()
);
}; };
return ( return (
<IconButton label='Insert Code' onClick={handleClick}> <IconButton label='Insert Code' onClick={handleClick}>
@ -102,17 +81,15 @@ const CodeApplyButton = ({ code, language, platform }) => {
); );
}; };
const FileApplyButton = ({ code, language, platform }) => { const FileApplyButton = ({ code }) => {
const {config} = useMst();
const handleClick = () => { const handleClick = () => {
const e = 'code_file_apply'; const e = 'code_file_apply';
messageUtil.sendMessage({ messageUtil.sendMessage({
command: e, command: e,
content: code content: code
}); });
APIUtil.createEvent( APIUtil.createEvent({name: e, value: e})
{name: e, value: code, language: language, ide: platform},
APIUtil.getCurrentMessageId()
);
}; };
return ( return (
<IconButton label='Replace File' onClick={handleClick}> <IconButton label='Replace File' onClick={handleClick}>
@ -122,7 +99,8 @@ const FileApplyButton = ({ code, language, platform }) => {
}; };
// Add a new button to create new file // Add a new button to create new file
const NewFileButton = ({ code, language, platform }) => { const NewFileButton = ({ language,code }) => {
const {config} = useMst();
const handleClick = () => { const handleClick = () => {
const e = 'code_new_file'; const e = 'code_new_file';
messageUtil.sendMessage({ messageUtil.sendMessage({
@ -130,10 +108,7 @@ const NewFileButton = ({ code, language, platform }) => {
language: language, language: language,
content: code content: code
}); });
APIUtil.createEvent( APIUtil.createEvent({name: e, value: e})
{name: e, value: code, language: language, ide: platform},
APIUtil.getCurrentMessageId()
);
}; };
return ( return (
<IconButton label='Create New File' onClick={handleClick}> <IconButton label='Create New File' onClick={handleClick}>
@ -143,7 +118,7 @@ const NewFileButton = ({ code, language, platform }) => {
}; };
// Similar changes can be made to DiffButton, CodeApplyButton, FileApplyButton, and CodeCopyButton // Similar changes can be made to DiffButton, CodeApplyButton, FileApplyButton, and CodeCopyButton
const CodeButtons = ({ platform, language, code }) => ( const CodeButtons = ({ language, code }) => (
<Flex <Flex
gap="5px" gap="5px"
justify="flex-start" justify="flex-start"
@ -152,16 +127,17 @@ const CodeButtons = ({ platform, language, code }) => (
wrap="wrap" wrap="wrap"
style={{ position: 'absolute', top: 8, right: 10 }} style={{ position: 'absolute', top: 8, right: 10 }}
> >
<CodeCopyButton code={code} language={language} platform={platform} /> <CodeCopyButton code={code} />
<> {language && language === 'commitmsg'
<DiffButton code={code} language={language} platform={platform} /> ? <CommitButton code={code} />
{language === 'edits' && ( : (
<EditApplyButton code={code} language={language} platform={platform} /> <>
<DiffButton code={code} />
<CodeApplyButton code={code} />
<FileApplyButton code={code} />
<NewFileButton code={code} language={language} />
</>
)} )}
<CodeApplyButton code={code} language={language} platform={platform} />
<FileApplyButton code={code} language={language} platform={platform} />
<NewFileButton code={code} language={language} platform={platform} />
</>
</Flex> </Flex>
); );

View File

@ -18,7 +18,6 @@ import { useTranslation } from "react-i18next";
import { useRouter } from "@/views/router"; import { useRouter } from "@/views/router";
import remarkGfm from "remark-gfm"; import remarkGfm from "remark-gfm";
import APIUtil from "@/util/APIUtil"; import APIUtil from "@/util/APIUtil";
import { ASSISTANT_DISPLAY_NAME } from "@/util/constants";
(typeof global !== "undefined" ? global : window).Prism = Prism; (typeof global !== "undefined" ? global : window).Prism = Prism;
require("prismjs/components/prism-java"); require("prismjs/components/prism-java");
@ -109,15 +108,12 @@ const MessageMarkdown = observer((props: MessageMarkdownProps) => {
}); });
}; };
const handleCodeCopy = (platform, language) => { const handleCodeCopy = (event) => {
const selection = window.getSelection()?.toString(); const selection = window.getSelection()?.toString();
console.log("Copied: ", selection); console.log("Copied: ", selection);
const e = 'manual_copy'; const e = 'manual_copy';
APIUtil.createEvent( APIUtil.createEvent({name: e, value: selection})
{name: e, value: selection, language: language, ide: platform}, }
APIUtil.getCurrentMessageId()
);
};
useEffect(() => { useEffect(() => {
let previousNode: any = null; let previousNode: any = null;
@ -172,7 +168,7 @@ const MessageMarkdown = observer((props: MessageMarkdownProps) => {
"Do you want to write some code or have a question about the project? " "Do you want to write some code or have a question about the project? "
) )
) { ) {
return t("devchat.help", {assistantName: t(ASSISTANT_DISPLAY_NAME)}) + chat.helpWorkflowCommands(); return t("devchat.help") + chat.helpWorkflowCommands();
} }
if ( if (
children.includes( children.includes(
@ -180,23 +176,23 @@ const MessageMarkdown = observer((props: MessageMarkdownProps) => {
) )
) { ) {
if (process.env.platform === "vscode") { if (process.env.platform === "vscode") {
return t("devchat.setkey_vscode", {assistantName: t(ASSISTANT_DISPLAY_NAME)}); return t("devchat.setkey_vscode");
} }
return t("devchat.setkey", {assistantName: t(ASSISTANT_DISPLAY_NAME)}); return t("devchat.setkey");
} }
if ( if (
children.includes( children.includes(
"DevChat intelligently navigates your codebase using GPT-4." "DevChat intelligently navigates your codebase using GPT-4."
) )
) { ) {
return t("ask-code-explain", {assistantName: t(ASSISTANT_DISPLAY_NAME)}); return t("ask-code-explain");
} }
if ( if (
children.includes( children.includes(
"Use this DevChat workflow to request code writing. Please input your specific requirement" "Use this DevChat workflow to request code writing. Please input your specific requirement"
) )
) { ) {
return t("code-explain", {assistantName: t(ASSISTANT_DISPLAY_NAME)}); return t("code-explain");
} }
if ( if (
children.includes( children.includes(
@ -263,18 +259,18 @@ const MessageMarkdown = observer((props: MessageMarkdownProps) => {
code({ node, inline, className, children, index, ...props }) { code({ node, inline, className, children, index, ...props }) {
const match = /language-(\w+)/.exec(className || ""); const match = /language-(\w+)/.exec(className || "");
const value = String(children).replace(/\n$/, ""); const value = String(children).replace(/\n$/, "");
let language = match && match[1]; let lanugage = match && match[1];
if (!language) { if (!lanugage) {
language = "plaintext"; lanugage = "plaintext";
} }
let wrapLongLines = false; let wrapLongLines = false;
if (language === "markdown" || language === "text") { if (lanugage === "markdown" || lanugage === "text") {
wrapLongLines = true; wrapLongLines = true;
} }
if (language === "step" || language === "Step") { if (lanugage === "step" || lanugage === "Step") {
const status = const status =
activeStep && activeStep &&
Number(index) === codes.length && Number(index) === codes.length &&
@ -282,13 +278,13 @@ const MessageMarkdown = observer((props: MessageMarkdownProps) => {
? "running" ? "running"
: "done"; : "done";
return ( return (
<Step language={language} status={status} index={index}> <Step language={lanugage} status={status} index={index}>
{value} {value}
</Step> </Step>
); );
} }
if (language === "chatmark" || language === "ChatMark") { if (lanugage === "chatmark" || lanugage === "ChatMark") {
const chatmarkValue = chatmarkProps[`chatmark-${index}`]; const chatmarkValue = chatmarkProps[`chatmark-${index}`];
return ( return (
<ChatMark messageDone={messageDone} {...chatmarkValue}> <ChatMark messageDone={messageDone} {...chatmarkValue}>
@ -297,17 +293,17 @@ const MessageMarkdown = observer((props: MessageMarkdownProps) => {
); );
} }
if ((language === "yaml" || language === "YAML") && props.hidden) { if ((lanugage === "yaml" || lanugage === "YAML") && props.hidden) {
return <></>; return <></>;
} }
return !inline && language ? ( return !inline && lanugage ? (
<div <div
style={{ position: "relative" }} style={{ position: "relative" }}
className={classes.codeOverride} className={classes.codeOverride}
> >
<LanguageCorner language={language} /> <LanguageCorner language={lanugage} />
<CodeButtons platform={platform === "idea" ? "intellij" : platform} language={language} code={value} /> <CodeButtons language={lanugage} code={value} />
{/* <SyntaxHighlighter {/* <SyntaxHighlighter
{...props} {...props}
language={lanugage} language={lanugage}
@ -321,7 +317,7 @@ const MessageMarkdown = observer((props: MessageMarkdownProps) => {
<Highlight <Highlight
code={value} code={value}
theme={themes.okaidia} theme={themes.okaidia}
language={language} language={lanugage}
> >
{({ {({
className, className,
@ -340,7 +336,7 @@ const MessageMarkdown = observer((props: MessageMarkdownProps) => {
whiteSpace: "pre", whiteSpace: "pre",
...props.style, ...props.style,
}} }}
onCopy={() => handleCodeCopy(platform, language)} onCopy={handleCodeCopy}
{...props} {...props}
> >
{tokens.map((line, i) => ( {tokens.map((line, i) => (
@ -410,20 +406,9 @@ const MessageMarkdown = observer((props: MessageMarkdownProps) => {
{children} {children}
</Anchor> </Anchor>
) : ( ) : (
<Anchor <a {...props} href={href} className={className}>
className={classes.link}
href="javascript:void()"
onClick={() => {
if (href) {
messageUtil.sendMessage({
command: "openLink",
url: href,
});
}
}}
>
{children} {children}
</Anchor> </a>
); );
}, },
img({ node, ...props }) { img({ node, ...props }) {

View File

@ -1,7 +1 @@
{ {}
"config.api_base": "API Base of {{assistantName}}",
"config.custom_api_base": "Custom API Base of {{assistantName}}",
"config.access_key": "Access Key of {{assistantName}}",
"devchat.help_question": "How do I use {{assistantName}}?",
"devchat.input_placeholder": "Ask {{assistantName}} a question or type / for workflow"
}

View File

@ -3,7 +3,6 @@ import { initReactI18next } from "react-i18next";
import enTranslations from "./en.json"; import enTranslations from "./en.json";
import zhTranslations from "./zh.json"; import zhTranslations from "./zh.json";
import { ASSISTANT_DISPLAY_NAME, ASSISTANT_DISPLAY_NAME_ZH } from "@/util/constants";
i18n.use(initReactI18next).init({ i18n.use(initReactI18next).init({
resources: { resources: {
@ -24,6 +23,5 @@ i18n.use(initReactI18next).init({
escapeValue: false, escapeValue: false,
}, },
}); });
i18n.addResource('zh', 'translation', ASSISTANT_DISPLAY_NAME, ASSISTANT_DISPLAY_NAME_ZH);
export default i18n; export default i18n;

View File

@ -5,12 +5,12 @@
"Refilled": "已重新填入", "Refilled": "已重新填入",
"Refill prompt": "重新填进输入框", "Refill prompt": "重新填进输入框",
"User": "用户", "User": "用户",
"devchat.input_placeholder": "向 {{assistantName}} 直接提问或输入 '/' 以查看可用的工作流", "Ask DevChat a question or type / for workflow": "向 DevChat 直接提问或输入 '/' 以查看可用的工作流",
"How do I use DevChat?": "如何使用 DevChat",
"balance": "你的账户余额为 {{formatedCurrency}},登录 <4>web.devchat.ai</4> 获得更多 tokens", "balance": "你的账户余额为 {{formatedCurrency}},登录 <4>web.devchat.ai</4> 获得更多 tokens",
"devchat.help_question": "如何使用 {{assistantName}}", "devchat.help": "你想生成一些代码还是对这个项目有疑问?请首先右键单击相关的文件或代码片段,将它们添加到 DevChat 中作为上下文,然后在输入框中写下你的请求或问题,我将基于添加的上下文生成代码或回答你的问题。<br> <br> 此外,在输入框中键入“/”DevChat 会列出可供使用的各类工作流,按 Tab 键或输入完整命令触发你需要的工作流。聊天愉快! <br> <br>下面是一些工作流的示例:<br> <br> ",
"devchat.help": "你想生成一些代码还是对这个项目有疑问?请首先右键单击相关的文件或代码片段,将它们添加到 {{assistantName}} 中作为上下文,然后在输入框中写下你的请求或问题,我将基于添加的上下文生成代码或回答你的问题。<br> <br> 此外,在输入框中键入“/”,{{assistantName}} 会列出可供使用的各类工作流,按 Tab 键或输入完整命令触发你需要的工作流。聊天愉快! <br> <br>下面是一些工作流的示例:<br> <br> ", "devchat.setkey": "你的环境或设置中缺少 DevChat 访问密钥。请输入你的 DevChat 访问密钥,这样我就可以开始正常工作了。<br> <br> <button value=\"get_devchat_key\">注册 DevChat 访问密钥</button> <button value=\"setting_devchat_key\">设置 DevChat 访问密钥</button>",
"devchat.setkey": "你的环境或设置中缺少 {{assistantName}} 访问密钥。请输入你的 {{assistantName}} 访问密钥,这样我就可以开始正常工作了。<br> <br> <button value=\"get_devchat_key\">注册 {{assistantName}} 访问密钥</button> <button value=\"setting_devchat_key\">设置 {{assistantName}} 访问密钥</button>", "devchat.setkey_vscode": "你的环境或设置中缺少 DevChat 访问密钥。请输入你的 DevChat 访问密钥,这样我就可以开始正常工作了。<br> <br> <button value=\"get_devchat_key\" component=\"a\" href=\"https://web.devchat.ai\">注册 DevChat 访问密钥</button> <button value=\"setting_devchat_key\">设置 DevChat 访问密钥</button>",
"devchat.setkey_vscode": "你的环境或设置中缺少 {{assistantName}} 访问密钥。请输入你的 {{assistantName}} 访问密钥,这样我就可以开始正常工作了。<br> <br> <button value=\"get_devchat_key\" component=\"a\" href=\"https://web.devchat.ai\">注册 {{assistantName}} 访问密钥</button> <button value=\"setting_devchat_key\">设置 {{assistantName}} 访问密钥</button>",
"Is DevChat Access Key ready?": "DevChat 访问密钥是否已经设置好?", "Is DevChat Access Key ready?": "DevChat 访问密钥是否已经设置好?",
"Ask questions about the current project's codebase, which requires proactive acquisition of additional context information to answer.": "询问关于当前项目代码库的问题,我将主动获取相关的上下文信息来回答。", "Ask questions about the current project's codebase, which requires proactive acquisition of additional context information to answer.": "询问关于当前项目代码库的问题,我将主动获取相关的上下文信息来回答。",
"Generate code with a general template embedded into the prompt.": "使用隐式嵌入到提示词中的通用模板生成代码。", "Generate code with a general template embedded into the prompt.": "使用隐式嵌入到提示词中的通用模板生成代码。",
@ -19,23 +19,23 @@
"Generate a commit message for the given git diff.": "为给定的 git diff 生成提交消息。", "Generate a commit message for the given git diff.": "为给定的 git diff 生成提交消息。",
"Generate a release note for the given commit log.": "为给定的提交日志生成发布说明。", "Generate a release note for the given commit log.": "为给定的提交日志生成发布说明。",
"Explain /ask-code": "解释 /ask-code", "Explain /ask-code": "解释 /ask-code",
"ask-code-explain": "向我们的 AI 代理提出有关您代码库的任何问题,并获得答案。<br> <br>{{assistantName}} 利用 GPT-4 智能地导航您的代码库。它会自动选择和分析最多十个与您的问题最相关的源文件来提供答案。敬请期待 — 我们即将整合更高效的 LLama 2 - 70B 模型。<br> <br> 示例问题:<br> -为什么更改的引导时间有时会显示为 null<br> -store.findAllAccounts 是如何实现的?<br> -当前的递归检索器会丢弃所有 TextNodes只查询 IndexNodes。这是一个 bug。我们该如何修复它", "ask-code-explain": "向我们的 AI 代理提出有关您代码库的任何问题,并获得答案。<br> <br>DevChat 利用 GPT-4 智能地导航您的代码库。它会自动选择和分析最多十个与您的问题最相关的源文件来提供答案。敬请期待 — 我们即将整合更高效的 LLama 2 - 70B 模型。<br> <br> 示例问题:<br> -为什么更改的引导时间有时会显示为 null<br> -store.findAllAccounts 是如何实现的?<br> -当前的递归检索器会丢弃所有 TextNodes只查询 IndexNodes。这是一个 bug。我们该如何修复它",
"Explain /release_note": "解释 /release_note", "Explain /release_note": "解释 /release_note",
"note-explain": "使用这个工作流程,您可以生成专业的、格式化的发布说明(markdown格式)。我只需要关于本次发布的提交的一些基本信息。通过点击“+”按钮并选择 git_log_releasenote 将这些信息添加到上下文中。如果提交的范围与默认命令不同,您还可以选择 <custom command> 并输入如 git log 579398b^..HEAD --pretty=format:\"%h - %B\" 的命令行来包含从提交 579398b(包括此提交)到最新提交的所有变更。", "note-explain": "使用这个工作流程,您可以生成专业的、格式化的发布说明(markdown格式)。我只需要关于本次发布的提交的一些基本信息。通过点击“+”按钮并选择 git_log_releasenote 将这些信息添加到上下文中。如果提交的范围与默认命令不同,您还可以选择 <custom command> 并输入如 git log 579398b^..HEAD --pretty=format:\"%h - %B\" 的命令行来包含从提交 579398b(包括此提交)到最新提交的所有变更。",
"Explain /code": "解释 /code", "Explain /code": "解释 /code",
"code-explain": "使用 {{assistantName}} 工作流程来请求编写代码。请输入您的具体需求,并提供适当的实施上下文。您可以选择相关的代码或文件,然后右键点击“添加到 {{assistantName}}”。如果您发现上下文仍然不足以解释清楚,您可以通过提供所选代码的类/函数定义来增强我对您代码的理解。要做到这一点,请点击所选代码旁边的“+”按钮,然后选择“符号定义”。请注意,这些信息显示在 {{assistantName}} 中可能需要几秒钟时间。", "code-explain": "使用 DevChat 工作流程来请求编写代码。请输入您的具体需求,并提供适当的实施上下文。您可以选择相关的代码或文件,然后右键点击“添加到 DevChat”。如果您发现上下文仍然不足以解释清楚,您可以通过提供所选代码的类/函数定义来增强我对您代码的理解。要做到这一点,请点击所选代码旁边的“+”按钮,然后选择“符号定义”。请注意,这些信息显示在 DevChat 中可能需要几秒钟时间。",
"Config": "配置", "Config": "配置",
"Singapore Node": "新加坡区节点", "Singapore Node": "新加坡区节点",
"China Node": "中国区节点", "China Node": "中国区节点",
"Custom": "自定义", "Custom": "自定义",
"config.custom_api_base": "自定义 {{assistantName}} API 地址", "Custom API Base of Devchat": "自定义 DevChat API 地址",
"config.api_base": "{{assistantName}} API 地址", "API Base of Devchat": "DevChat API 地址",
"the base URL for the API": "API 的基础 URL", "the base URL for the API": "API 的基础 URL",
"Access Key of OpenAI": "OpenAI 访问密钥", "Access Key of OpenAI": "OpenAI 访问密钥",
"Your Access Key": "你的访问密钥", "Your Access Key": "你的访问密钥",
"please keep this secret": "请不要泄露给他人", "please keep this secret": "请不要泄露给他人",
"API Base of OpenAI": "OpenAI API 地址", "API Base of OpenAI": "OpenAI API 地址",
"config.access_key": "{{assistantName}} 访问密钥", "Access Key of Devchat": "DevChat 访问密钥",
"Model Config": "模型配置", "Model Config": "模型配置",
"/1M tokens": "/每百万token", "/1M tokens": "/每百万token",
"output price:": "输出价格:", "output price:": "输出价格:",
@ -59,15 +59,20 @@
"Please enter the proxy url and port": "请输入您的代理URL和端口", "Please enter the proxy url and port": "请输入您的代理URL和端口",
"Cancel": "取消", "Cancel": "取消",
"Save": "保存", "Save": "保存",
"Reload built-in & custom workflows": "重新加载内置和自定义工作流",
"Sync settings from cloud": "从云端同步设置",
"Max input tokens": "最大输入数", "Max input tokens": "最大输入数",
"/unit_tests": "/生成单测",
"Generate unit tests.": "为函数生成Happy Path和Edge Case测试用例。", "Generate unit tests.": "为函数生成Happy Path和Edge Case测试用例。",
"/commit": "/提交信息",
"Writes a well-formatted commit message for selected code changes and commits them via Git. Include an issue number if desired (e.g., input \"/commit to close #12\").": "为变更代码生成提交消息。", "Writes a well-formatted commit message for selected code changes and commits them via Git. Include an issue number if desired (e.g., input \"/commit to close #12\").": "为变更代码生成提交消息。",
"/docstring": "/函数注释",
"Automatically add docstrings. Select a function or method and execute this command to generate docstring.": "对所选函数生成函数注释。", "Automatically add docstrings. Select a function or method and execute this command to generate docstring.": "对所选函数生成函数注释。",
"/comments": "/行间注释",
"Automatically add doc comments. Select some code and execute this command to generate comments.": "对所选代码生成行间注释。", "Automatically add doc comments. Select some code and execute this command to generate comments.": "对所选代码生成行间注释。",
"/fix": "/代码纠错",
"Try to find out potential bugs in the selected code and try fixing these bugs automaticly.": "对所选代码进行自动修复。", "Try to find out potential bugs in the selected code and try fixing these bugs automaticly.": "对所选代码进行自动修复。",
"/explain": "/代码解释",
"Explain selected code.": "对所选代码生成解释。", "Explain selected code.": "对所选代码生成解释。",
"/refactor": "/代码重构",
"rewrite selected code.": "对所选代码进行重构。", "rewrite selected code.": "对所选代码进行重构。",
"workflowTip": "为了确保高准确度,我们的 {{name}} 采纳了包括规划、工具使用、反思在内的LLM设计模式。这些模式能显著提高回应的精确性但请注意由于深度处理的需要回应时间可能超过<3>1分钟</3>。" "workflowTip": "为了确保高准确度,我们的 {{name}} 采纳了包括规划、工具使用、反思在内的LLM设计模式。这些模式能显著提高回应的精确性但请注意由于深度处理的需要回应时间可能超过<3>1分钟</3>。"
} }

View File

@ -23,7 +23,6 @@ import {
IconExternalLink, IconExternalLink,
} from "@tabler/icons-react"; } from "@tabler/icons-react";
import { useRouter } from "../router"; import { useRouter } from "../router";
import IDEServiceUtil from "@/util/IDEServiceUtil";
interface WorkflowConf { interface WorkflowConf {
description: string; description: string;
@ -54,6 +53,12 @@ const chatPanel = observer(() => {
behavior: "smooth", behavior: "smooth",
}); });
const getFeatureToggles = () => {
messageUtil.sendMessage({
command: "featureToggles",
});
};
const timer = useTimeout(() => { const timer = useTimeout(() => {
if (chat.isBottom) { if (chat.isBottom) {
scrollToBottom(); scrollToBottom();
@ -109,6 +114,8 @@ const chatPanel = observer(() => {
}); });
}); });
chat.fetchHistoryMessages(); chat.fetchHistoryMessages();
input.fetchContextMenus().then();
getFeatureToggles();
} }
); );
messageUtil.registerHandler( messageUtil.registerHandler(
@ -142,49 +149,17 @@ const chatPanel = observer(() => {
input.clearContexts(); input.clearContexts();
} }
); );
messageUtil.registerHandler(
"featureToggles",
(message: { features: object }) => {
chat.updateFeatures(message.features);
}
);
messageUtil.registerHandler("codeDiffApply", (_: any) => { messageUtil.registerHandler("codeDiffApply", (_: any) => {
const e = 'code_diff_apply'; const e = 'code_diff_apply'
const platform = process.env.platform === "idea" ? "intellij" : process.env.platform; APIUtil.createEvent({name: e, value: e})
IDEServiceUtil.getCurrentFileInfo().then(info => APIUtil.createEvent({ })
name: e,
value: e,
ide: platform,
language: info?.extension || info?.path?.split('.').pop()
}));
});
messageUtil.registerHandler("logEvent", (message: {id: string, language: string, name: string, value: Record<string, any>}) => {
const platform = process.env.platform === "idea" ? "intellij" : process.env.platform;
APIUtil.createEvent({
name: message.name,
value: typeof message.value === 'string' ? message.value : JSON.stringify(message.value),
ide: platform,
language: message.language
}, message.id);
console.log("logEvent:", message);
});
messageUtil.registerHandler("logMessage", (message: {id: string, language: string, commandName: string, content: string, model: string}) => {
const platform = process.env.platform === "idea" ? "intellij" : process.env.platform;
const timestamp = new Date().toISOString(); // 自动生成当前时间的ISO格式时间戳
APIUtil.createMessage({
content: message.content,
command: message.commandName,
timestamp: timestamp,
ide: platform,
language: message.language,
model: message.model
}, message.id);
console.log("logMessage:", { message, timestamp });
});
messageUtil.registerHandler("getIDEServicePort", (data: any) => {
IDEServiceUtil.config(data.result);
});
messageUtil.sendMessage({ command: "getIDEServicePort" });
messageUtil.sendMessage({ command: "regCommandList" }); messageUtil.sendMessage({ command: "regCommandList" });
timer.start(); timer.start();

View File

@ -25,7 +25,6 @@ import cloneDeep from "lodash.clonedeep";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import { useDisclosure } from "@mantine/hooks"; import { useDisclosure } from "@mantine/hooks";
import { observer } from "mobx-react-lite"; import { observer } from "mobx-react-lite";
import { ASSISTANT_DISPLAY_NAME } from "@/util/constants";
const commonInputStyle = { const commonInputStyle = {
label: { label: {
@ -112,6 +111,10 @@ const Config = observer(() => {
const [current, setCurrent] = useState(""); const [current, setCurrent] = useState("");
useEffect(() => { useEffect(() => {
MessageUtil.registerHandler("updateSetting", (data) => {
// 保存后的回调
MessageUtil.sendMessage({ command: "readConfig" });
});
if (router.currentRoute !== "config") {return;} if (router.currentRoute !== "config") {return;}
const modelArray = config.modelsTemplate.map((item) => ({ const modelArray = config.modelsTemplate.map((item) => ({
value: item.name, value: item.name,
@ -157,28 +160,11 @@ const Config = observer(() => {
key: "", key: "",
}); });
setTimeout(() => { setTimeout(() => {
MessageUtil.handleMessage({ command: "reloadConfig" }); config.setTemplate([], "");
MessageUtil.sendMessage({ command: "readConfig", key: "" });
}, 1000); }, 1000);
}; };
const handleSync = () => {
config.updateSettle(false);
startLoading();
// 调用 Local Service 更新工作流,更新、重载命令列表
MessageUtil.handleMessage({ command: "reloadConfig" });
};
const handleReload = () => {
config.updateSettle(false);
startLoading();
// update workflow list
config.updateWorkflowList().then(() => {
config.updateSettle(true);
router.updateRoute("chat");
closeLoading();
});
};
const changeModelDetail = (key: string, value: number | string) => { const changeModelDetail = (key: string, value: number | string) => {
const newModel = { ...form.values.models[current], [key]: value }; const newModel = { ...form.values.models[current], [key]: value };
form.setFieldValue("models", { form.setFieldValue("models", {
@ -237,7 +223,7 @@ const Config = observer(() => {
> >
<Stack> <Stack>
<Tabs <Tabs
defaultValue={ASSISTANT_DISPLAY_NAME} defaultValue="Devchat"
variant="outline" variant="outline"
sx={{ sx={{
".mantine-UnstyledButton-root::before": { ".mantine-UnstyledButton-root::before": {
@ -247,12 +233,12 @@ const Config = observer(() => {
> >
<Tabs.List> <Tabs.List>
<Tabs.Tab <Tabs.Tab
value={ASSISTANT_DISPLAY_NAME} value="Devchat"
sx={{ sx={{
color: "var(--vscode-editor-foreground)", color: "var(--vscode-editor-foreground)",
}} }}
> >
{ASSISTANT_DISPLAY_NAME} Devchat
</Tabs.Tab> </Tabs.Tab>
<Tabs.Tab <Tabs.Tab
value="OpenAI" value="OpenAI"
@ -264,7 +250,7 @@ const Config = observer(() => {
</Tabs.Tab> </Tabs.Tab>
</Tabs.List> </Tabs.List>
<Tabs.Panel <Tabs.Panel
value={ASSISTANT_DISPLAY_NAME} value="Devchat"
pt="xs" pt="xs"
p={10} p={10}
sx={{ sx={{
@ -281,7 +267,7 @@ const Config = observer(() => {
...selectStyle, ...selectStyle,
}} }}
placeholder="https://xxxx.xx" placeholder="https://xxxx.xx"
label={t("config.api_base", {assistantName: t(ASSISTANT_DISPLAY_NAME)})} label={t("API Base of Devchat")}
withAsterisk withAsterisk
description={t("the base URL for the API")} description={t("the base URL for the API")}
{...form.getInputProps("providers.devchat.api_base")} {...form.getInputProps("providers.devchat.api_base")}
@ -289,7 +275,7 @@ const Config = observer(() => {
{form.values.providers?.devchat?.api_base === "custom" && ( {form.values.providers?.devchat?.api_base === "custom" && (
<TextInput <TextInput
styles={commonInputStyle} styles={commonInputStyle}
label={t("config.custom_api_base", {assistantName: t(ASSISTANT_DISPLAY_NAME)})} label={t("Custom API Base of Devchat")}
withAsterisk withAsterisk
description={t("the base URL for the API")} description={t("the base URL for the API")}
{...form.getInputProps( {...form.getInputProps(
@ -307,7 +293,7 @@ const Config = observer(() => {
}, },
}} }}
withAsterisk withAsterisk
label={t("config.access_key", {assistantName: t(ASSISTANT_DISPLAY_NAME)})} label={t("Access Key of Devchat")}
placeholder={t("Your Access Key")} placeholder={t("Your Access Key")}
description={t("please keep this secret")} description={t("please keep this secret")}
{...form.getInputProps("providers.devchat.api_key")} {...form.getInputProps("providers.devchat.api_key")}
@ -395,7 +381,7 @@ const Config = observer(() => {
...selectStyle, ...selectStyle,
}} }}
data={[ data={[
{ value: "devchat", label: ASSISTANT_DISPLAY_NAME }, { value: "devchat", label: "Devchat" },
{ value: "openai", label: "OpenAI" }, { value: "openai", label: "OpenAI" },
]} ]}
value={form.values?.models[current]?.provider} value={form.values?.models[current]?.provider}
@ -431,6 +417,13 @@ const Config = observer(() => {
description={t("Please enter the path of your python")} description={t("Please enter the path of your python")}
{...form.getInputProps("python_for_chat")} {...form.getInputProps("python_for_chat")}
/> />
<TextInput
styles={commonInputStyle}
label={t("Python for commands")}
placeholder="/xxx/xxx"
description={t("Please enter the path of your python")}
{...form.getInputProps("python_for_commands")}
/>
<TextInput <TextInput
styles={commonInputStyle} styles={commonInputStyle}
label={t("Proxy setting")} label={t("Proxy setting")}
@ -470,18 +463,6 @@ const Config = observer(() => {
/> />
</> </>
)} )}
<Button
onClick={handleSync}
variant="outline"
color="gray">
{t("Sync settings from cloud")}
</Button>
<Button
onClick={handleReload}
variant="outline"
color="gray">
{t("Reload built-in & custom workflows")}
</Button>
</Stack> </Stack>
<Group <Group
grow grow
@ -502,7 +483,6 @@ const Config = observer(() => {
variant="outline" variant="outline"
color="gray" color="gray"
onClick={() => { onClick={() => {
form.reset();
router.updateRoute("chat"); router.updateRoute("chat");
}} }}
> >

View File

@ -5,9 +5,6 @@ import yaml from "js-yaml";
import { RootInstance } from "./RootStore"; import { RootInstance } from "./RootStore";
import i18next from "i18next"; import i18next from "i18next";
import APIUtil from "@/util/APIUtil"; import APIUtil from "@/util/APIUtil";
import IDEServiceUtil from "@/util/IDEServiceUtil";
import { ASSISTANT_DISPLAY_NAME } from "@/util/constants";
import { v4 as uuidv4 } from 'uuid';
interface Context { interface Context {
content: string; content: string;
@ -101,6 +98,7 @@ export const ChatStore = types
chatPanelWidth: 300, chatPanelWidth: 300,
disabled: false, disabled: false,
rechargeSite: "https://web.devchat.ai/pricing/", rechargeSite: "https://web.devchat.ai/pricing/",
features: types.optional(types.frozen(), {}),
key: types.optional(types.string, ""), key: types.optional(types.string, ""),
}) })
.actions((self) => { .actions((self) => {
@ -173,10 +171,8 @@ export const ChatStore = types
self.hasDone = false; self.hasDone = false;
self.errorMessage = ""; self.errorMessage = "";
self.currentMessage = ""; self.currentMessage = "";
const rootStore = getParent<RootInstance>(self); const config = getParent<RootInstance>(self).config
const config = rootStore.config;
const chatModel = config.getDefaultModel(); const chatModel = config.getDefaultModel();
const platform = process.env.platform;
messageUtil.sendMessage({ messageUtil.sendMessage({
command: "sendMessage", command: "sendMessage",
text: text, text: text,
@ -184,22 +180,12 @@ export const ChatStore = types
parent_hash: lastNonEmptyHash(), parent_hash: lastNonEmptyHash(),
model: chatModel, model: chatModel,
}); });
const supportedCommands = new Set(rootStore.input.commandMenus.map(x => x.name)); APIUtil.createMessage({content: text, model: chatModel});
let command = text.startsWith("/") ? text.split(" ", 1)[0] : null;
command = command && supportedCommands.has(command.slice(1)) ? command : null;
IDEServiceUtil.getCurrentFileInfo().then(info => APIUtil.createMessage({
content: text,
command: command,
model: chatModel,
language: info?.extension || info?.path?.split(".").pop(),
ide: platform === "idea" ? "intellij" : platform
}, APIUtil.updateCurrentMessageId()));
}; };
const helpMessage = (originalMessage = false) => { const helpMessage = (originalMessage = false) => {
let helps = ` 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 ${ASSISTANT_DISPLAY_NAME}. Feel free to ask me anything or let me help you with coding. 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.
To see a list of workflows you can run in the context, just type "/". Happy prompting! To see a list of workflows you can run in the context, just type "/". Happy prompting!
@ -208,17 +194,17 @@ To get started, here are some of the things that I can do for you:
${helpWorkflowCommands()}`; ${helpWorkflowCommands()}`;
const setKeyMessage = ` const setKeyMessage = `
Your ${ASSISTANT_DISPLAY_NAME} Access Key is not detected in the current settings. Please set your Access Key below, and we'll have everything set up for you in no time. Your DevChat Access Key is not detected in the current settings. Please set your Access Key below, and we'll have everything set up for you in no time.
<button value="get_devchat_key" ${ <button value="get_devchat_key" ${
process.env.platform === "vscode" process.env.platform === "vscode"
? 'href="https://web.devchat.ai" component="a"' ? 'href="https://web.devchat.ai" component="a"'
: "" : ""
}>Get ${ASSISTANT_DISPLAY_NAME} key</button> }>Get DevChat key</button>
<button value="setting_devchat_key">Set ${ASSISTANT_DISPLAY_NAME} key</button> <button value="setting_devchat_key">Set DevChat key</button>
`; `;
const setKeyUser = `Is ${ASSISTANT_DISPLAY_NAME} Access Key ready?`; const setKeyUser = `Is DevChat Access Key ready?`;
const accessKey = getParent<RootInstance>(self).config.getUserKey(); const accessKey = getParent<RootInstance>(self).config.getUserKey();
@ -239,7 +225,7 @@ Your ${ASSISTANT_DISPLAY_NAME} Access Key is not detected in the current setting
self.messages.push( self.messages.push(
Message.create({ Message.create({
type: "user", type: "user",
message: originalMessage ? i18next.t("devchat.help_question", {assistantName: i18next.t(ASSISTANT_DISPLAY_NAME)}) : "/help", message: originalMessage ? "How do I use DevChat?" : "/help",
}) })
); );
self.messages.push( self.messages.push(
@ -357,6 +343,9 @@ Thinking...
const rootStore = getParent<RootInstance>(self); const rootStore = getParent<RootInstance>(self);
rootStore.config.setConfigValue("default_model", chatModel); rootStore.config.setConfigValue("default_model", chatModel);
}, },
updateFeatures: (features: any) => {
self.features = features;
},
startSystemMessage: () => { startSystemMessage: () => {
self.generating = true; self.generating = true;
self.responsed = false; self.responsed = false;
@ -394,13 +383,6 @@ Thinking...
} else { } else {
self.messages[messagesLength - 1].message = message; self.messages[messagesLength - 1].message = message;
} }
// send event to server
const platform = process.env.platform === "idea" ? "intellij" : process.env.platform;
APIUtil.createEvent(
{name: 'stopGenerating', value: message, language: "unknow", ide: platform},
APIUtil.getCurrentMessageId()
);
}, },
newMessage: (message: IMessage) => { newMessage: (message: IMessage) => {

View File

@ -1,5 +1,4 @@
import MessageUtil from "@/util/MessageUtil"; import MessageUtil from "@/util/MessageUtil";
import IDEServiceUtil from "@/util/IDEServiceUtil";
import { types, Instance, flow } from "mobx-state-tree"; import { types, Instance, flow } from "mobx-state-tree";
import modelsTemplate from "@/models"; import modelsTemplate from "@/models";
import cloneDeep from "lodash.clonedeep"; import cloneDeep from "lodash.clonedeep";
@ -10,65 +9,20 @@ const defaultAPIBase = [
"https://api.devchat-ai.cn/v1", "https://api.devchat-ai.cn/v1",
]; ];
export const fetchLLMs = async ({modelsUrl,devchatApiKey}) => {
function deepCopy(obj) { return new Promise<{data:any}>((resolve, reject) => {
let copy = Array.isArray(obj) ? [] : {}; // 添加 header: "Authorization: Bearer ${devchatApiKey}"
axios.get(`${modelsUrl}/models`, { headers: { 'Authorization': `Bearer ${devchatApiKey}` }}).then((res) => {
for (let key in obj) { // 获取 models 模版列表
if (obj.hasOwnProperty(key)) { if (res?.data?.data && Array.isArray(res?.data?.data)) {
if (typeof obj[key] === 'object' && obj[key] !== null) { resolve(res.data);
copy[key] = deepCopy(obj[key]); // 递归调用 }
} else { }).catch((e) => {
copy[key] = obj[key]; console.error("fetchLLMs error:", e);
} reject(e);
});
} }
} );
return copy;
}
export const doUpdateWorkflowList = async () => {
try {
// Get local service port
const port = await IDEServiceUtil.callService("get_local_service_port", {});
// check whether port is valid
if (!port) {
console.error("do update workflow and command list error: port is invalid");
return undefined;
}
// Call local service to update Workflows
await axios.post(`http://localhost:${port}/workflows/update`, {});
await axios.post(`http://localhost:${port}/workflows/custom_update`, {});
// Update command list
MessageUtil.sendMessage({ command: "regCommandList" });
} catch (e) {
console.error("do update workflow and command list error:", e);
return undefined;
}
};
export const fetchServerConfigUtil = async ({ modelsUrl, devchatApiKey }) => {
try {
const response = await axios.get(`${modelsUrl}/models`, {
headers: { 'Authorization': `Bearer ${devchatApiKey}` }
});
if (response.status === 200) {
if (response.data.data && Array.isArray(response.data.data)) {
// change data to models key
response.data.models = response.data.data;
delete response.data.data;
}
return response.data;
} else {
console.error("fetchServerConfig error: Non-200 status code", response.status);
return undefined;
}
} catch (e) {
console.error("fetchServerConfig error:", e);
return undefined;
}
}; };
export const Model = types.model({ export const Model = types.model({
@ -99,27 +53,11 @@ export const ConfigStore = types
provider: "devchat", provider: "devchat",
}) })
.actions((self) => { .actions((self) => {
const setTemplate = (value: any,) => { const setTemplate = (value: any, provider: string) => {
const provider = self.provider;
const list: any[] = [];
for (const name in value) {
if (value.hasOwnProperty(name)) {
const item: any = { name };
for (const key in value[name]) {
if (value[name].hasOwnProperty(key)) {
item[key] = value[name][key];
}
}
list.push(item);
}
}
value = list;
const models = value const models = value
.map((item) => { .map((item) => {
return { return {
name: item.name, name: item.model ?? item.id,
max_input_tokens: item.max_input_tokens ?? 6000, max_input_tokens: item.max_input_tokens ?? 6000,
provider: provider, provider: provider,
stream: true, stream: true,
@ -137,13 +75,12 @@ export const ConfigStore = types
.filter((item) => item.category === "chat"); .filter((item) => item.category === "chat");
self.modelsTemplate = models; self.modelsTemplate = models;
}; };
const updateSettle = (value: boolean) => {
self.settle = value;
};
return { return {
setTemplate, setTemplate,
updateSettle, updateSettle: (value: boolean) => {
self.settle = value;
},
getDefaultModel: () => { getDefaultModel: () => {
return self.defaultModel; return self.defaultModel;
}, },
@ -175,104 +112,47 @@ export const ConfigStore = types
} }
return ""; return "";
}, },
updateConfig(server_config: any, server_config_base: any, user_config: any) {
console.log("----->:::updateConfig");
// 如果server_config没有获取到那么直接返回
if (!server_config || !server_config.models) {
return [undefined, user_config];
}
if (server_config_base === undefined) {
server_config_base = {};
}
if (server_config_base.models === undefined) {
server_config_base.models = {};
}
const provider = self.provider;
// 将 server_config 转换为本地配置存储的格式
const localConfig: any = {"models": {}};
server_config.models.forEach((model: any) => {
const modelConfig: any = {};
for (const key in model) {
if (key !== 'model') {
modelConfig[key] = model[key];
}
}
modelConfig["provider"] = provider;
modelConfig["stream"] = true;
localConfig["models"][model.model || model.id] = modelConfig;
});
// 合并 config 部分,不假设具体的配置项名称
for (const key in server_config.config) {
localConfig[key] = server_config.config[key];
}
// 使用子函数处理对比和更新
const userConfigNew = this.compareConfigs(localConfig, server_config_base, user_config);
return [localConfig, userConfigNew];
},
compareConfigs(localConfig: any, baseConfig: any, userConfigIn: any) {
let userConfig = { ...userConfigIn };
for (const key in localConfig) {
if (baseConfig.hasOwnProperty(key) && userConfig.hasOwnProperty(key)) {
// 递归比较对象的每个叶子结点
if (typeof localConfig[key] === 'object' && !Array.isArray(localConfig[key]) && localConfig[key] !== null) {
userConfig[key] = this.compareConfigs(localConfig[key], baseConfig[key], userConfig[key]);
} else {
if (localConfig[key] !== baseConfig[key]) {
// 检查用户配置中是否存在该条目
if (!userConfig[key] || JSON.stringify(userConfig[key]) === JSON.stringify(baseConfig[key])) {
userConfig[key] = localConfig[key];
}
}
}
} else {
// 新增的配置项
if (!userConfig[key]) {
if (!Object.isExtensible(userConfig)) {
// 如果 userConfig 不可扩展,创建一个新的可扩展对象
userConfig = { ...userConfig };
}
userConfig[key] = localConfig[key];
}
}
}
// 处理删除的配置项,仅针对 models 下的 model
if (localConfig.models ) {
const localModels = localConfig.models;
const userModels = userConfig.models;
for (const modelKey in userModels) {
if (!localModels.hasOwnProperty(modelKey)) {
// 删除的 model从 userConfig 中移除
delete userModels[modelKey];
}
}
}
return userConfig;
},
setConfig: function (data) { setConfig: function (data) {
this.setTemplate(data.models);
this.updateSettle(false); this.updateSettle(false);
const newConfig: any = deepCopy(data); let needUpdate = false;
const newConfig = { ...data };
newConfig.models = newConfig.models || {}; newConfig.models = newConfig.models || {};
newConfig.providers = newConfig.providers || {}; newConfig.providers = newConfig.providers || {};
if (!newConfig.providers.openai) { newConfig.providers.openai = newConfig.providers.openai || {
newConfig.providers.openai = { api_key: "",
api_key: "", api_base: "",
api_base: "", };
newConfig.providers.devchat = newConfig.providers.devchat || {
api_key: "",
api_base: "",
};
self.modelsTemplate.forEach((item) => {
const currentModel: any = {
...item,
}; };
} delete currentModel.name;
if (!newConfig.providers.devchat) {
newConfig.providers.devchat = { if (!newConfig.models[item.name]) {
api_key: "", newConfig.models[item.name] = {
api_base: "", ...currentModel,
}; };
} needUpdate = true;
}
if (newConfig.models[item.name].provider !== currentModel.provider) {
needUpdate = true;
newConfig.models[item.name].provider = currentModel.provider;
}
// 只有之前配置过 openai 的provider 才可以是 openai
if (
newConfig.models[item.name].provider === "openai" &&
!newConfig.providers.openai.api_key
) {
needUpdate = true;
newConfig.models[item.name].provider = "devchat";
}
});
// 尝试获取 devchat 的 api_base // 尝试获取 devchat 的 api_base
self.provider = "devchat"; self.provider = "devchat";
@ -292,35 +172,42 @@ export const ConfigStore = types
self.devchatApiKey = "1234"; self.devchatApiKey = "1234";
self.provider = "devchat"; self.provider = "devchat";
} }
const modelsChat = self.modelsTemplate.filter(model => model.category === "chat");
if (modelsChat.length > 0 && modelsChat.find((item) => item.name === newConfig.default_model) === undefined) {
const defaultModelName = 'qwen-72b-chat'
newConfig.default_model = modelsChat.some(x => x.name === defaultModelName) ? defaultModelName : modelsChat[0].name;
needUpdate = true;
}
if (!defaultAPIBase.includes(newConfig.providers.devchat.api_base)) { if (!defaultAPIBase.includes(newConfig.providers.devchat.api_base)) {
newConfig.providers.devchat.cumstom_api_base = newConfig.providers.devchat.cumstom_api_base =
newConfig.providers.devchat.api_base; newConfig.providers.devchat.api_base;
newConfig.providers.devchat.api_base = "custom"; newConfig.providers.devchat.api_base = "custom";
} }
if (this.checkAndSetCompletionDefaults(newConfig)) {
needUpdate = true;
}
self.config = newConfig; self.config = newConfig;
self.defaultModel = newConfig.default_model; self.defaultModel = newConfig.default_model;
this.writeConfig(); if (needUpdate) {
this.writeConfig();
}
this.updateSettle(true); this.updateSettle(true);
}, },
fetchServerConfig: flow(function* (){ refreshModelList: flow(function* (){
try { try {
const data = yield fetchServerConfigUtil({ modelsUrl: self.modelsUrl, devchatApiKey: self.devchatApiKey }); if (self.modelsTemplate.length === 0) {
if (data !== undefined) { const { data } = yield fetchLLMs({modelsUrl:self.modelsUrl,devchatApiKey:self.devchatApiKey});
MessageUtil.handleMessage({ command: "readServerConfig", value: data }); setTemplate(data,self.provider);
} else { MessageUtil.sendMessage({ command: "readConfig", key: "" });
console.log("fetchLLMs error: Failed to fetch server config");
MessageUtil.handleMessage({ command: "readServerConfig", value: undefined });
} }
} catch (e) { } catch (e) {
console.log("fetchLLMs error:", e); console.log("fetchLLMs error:", e);
MessageUtil.handleMessage({ command: "readServerConfig", value: undefined });
} }
}), }),
updateWorkflowList: flow(function* (){
yield doUpdateWorkflowList();
}),
checkAndSetCompletionDefaults: (newConfig) => { checkAndSetCompletionDefaults: (newConfig) => {
const codeModels = self.modelsTemplate.filter(model => model.category === "code"); const codeModels = self.modelsTemplate.filter(model => model.category === "code");
const isCustomAPIBase = self.modelsUrl.indexOf("api.devchat.ai") === -1 && self.modelsUrl.indexOf("api.devchat-ai.cn") === -1; const isCustomAPIBase = self.modelsUrl.indexOf("api.devchat.ai") === -1 && self.modelsUrl.indexOf("api.devchat-ai.cn") === -1;
@ -357,6 +244,10 @@ export const ConfigStore = types
value: writeConfig, value: writeConfig,
key: "", key: "",
}); });
setTimeout(() => {
MessageUtil.sendMessage({ command: "readConfig", key: "" });
}, 1000);
}, },
setConfigValue: function (key: string, value: any) { setConfigValue: function (key: string, value: any) {
if (key === "default_model") { if (key === "default_model") {

View File

@ -9,6 +9,22 @@ export interface Item {
recommend: number; recommend: number;
} }
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);
}
});
};
export const ChatContext = types.model({ export const ChatContext = types.model({
file: types.maybe(types.string), file: types.maybe(types.string),
path: types.maybe(types.string), path: types.maybe(types.string),
@ -24,6 +40,13 @@ export const MenuItem = types.model({
recommend: types.number, recommend: types.number,
}); });
export const ContextMenuItem = types.model({
icon: types.maybe(types.string),
name: types.string,
pattern: types.maybe(types.string),
description: types.string,
});
export const InputStore = types export const InputStore = types
.model("Input", { .model("Input", {
value: "", value: "",
@ -31,7 +54,8 @@ export const InputStore = types
menuType: "contexts", menuType: "contexts",
menuOpend: false, menuOpend: false,
currentMenuIndex: 0, currentMenuIndex: 0,
commandMenus: types.array(MenuItem) commandMenus: types.array(MenuItem),
contextMenus: types.array(ContextMenuItem)
}) })
.actions((self) => ({ .actions((self) => ({
setValue(value: string) { setValue(value: string) {
@ -63,6 +87,12 @@ export const InputStore = types
setCurrentMenuIndex(index: number) { setCurrentMenuIndex(index: number) {
self.currentMenuIndex = index; self.currentMenuIndex = index;
}, },
fetchContextMenus: flow(function* () {
try {
const items = yield regContextMenus();
self.contextMenus.push(...items);
} catch (error) {}
}),
fetchCommandMenus: (items: Item[]) => { fetchCommandMenus: (items: Item[]) => {
self.commandMenus.clear(); self.commandMenus.clear();
self.commandMenus.push(...items); self.commandMenus.push(...items);

View File

@ -4377,7 +4377,7 @@ glob@7.2.0:
once "^1.3.0" once "^1.3.0"
path-is-absolute "^1.0.0" path-is-absolute "^1.0.0"
glob@^7.0.0, glob@^7.0.3, glob@^7.1.3, glob@^7.1.4: glob@^7.0.3, glob@^7.1.3, glob@^7.1.4:
version "7.2.3" version "7.2.3"
resolved "https://registry.yarnpkg.com/glob/-/glob-7.2.3.tgz#b8df0fb802bbfa8e89bd1d938b4e16578ed44f2b" resolved "https://registry.yarnpkg.com/glob/-/glob-7.2.3.tgz#b8df0fb802bbfa8e89bd1d938b4e16578ed44f2b"
integrity sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q== integrity sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==
@ -4812,11 +4812,6 @@ inline-style-parser@0.1.1:
resolved "https://registry.yarnpkg.com/inline-style-parser/-/inline-style-parser-0.1.1.tgz#ec8a3b429274e9c0a1f1c4ffa9453a7fef72cea1" resolved "https://registry.yarnpkg.com/inline-style-parser/-/inline-style-parser-0.1.1.tgz#ec8a3b429274e9c0a1f1c4ffa9453a7fef72cea1"
integrity sha512-7NXolsK4CAS5+xvdj5OMMbI962hU/wvwoxk+LWR9Ek9bVtyuuYScDN6eS0rUm6TxApFpw7CX1o4uJzcd4AyD3Q== integrity sha512-7NXolsK4CAS5+xvdj5OMMbI962hU/wvwoxk+LWR9Ek9bVtyuuYScDN6eS0rUm6TxApFpw7CX1o4uJzcd4AyD3Q==
interpret@^1.0.0:
version "1.4.0"
resolved "https://registry.yarnpkg.com/interpret/-/interpret-1.4.0.tgz#665ab8bc4da27a774a40584e812e3e0fa45b1a1e"
integrity sha512-agE4QfB2Lkp9uICn7BAqoscw4SZP9kTE2hxiFI3jBPmXJfdqiahTbUuKGsMoN2GtqL9AxhYioAcVvgsb1HvRbA==
interpret@^3.1.1: interpret@^3.1.1:
version "3.1.1" version "3.1.1"
resolved "https://registry.yarnpkg.com/interpret/-/interpret-3.1.1.tgz#5be0ceed67ca79c6c4bc5cf0d7ee843dcea110c4" resolved "https://registry.yarnpkg.com/interpret/-/interpret-3.1.1.tgz#5be0ceed67ca79c6c4bc5cf0d7ee843dcea110c4"
@ -6507,7 +6502,7 @@ minimatch@^5.0.1:
dependencies: dependencies:
brace-expansion "^2.0.1" brace-expansion "^2.0.1"
minimist@^1.2.3, minimist@^1.2.6: minimist@^1.2.6:
version "1.2.8" version "1.2.8"
resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.8.tgz#c1a464e7693302e082a075cee0c057741ac4772c" resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.8.tgz#c1a464e7693302e082a075cee0c057741ac4772c"
integrity sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA== integrity sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==
@ -7518,13 +7513,6 @@ readdirp@~3.6.0:
dependencies: dependencies:
picomatch "^2.2.1" picomatch "^2.2.1"
rechoir@^0.6.2:
version "0.6.2"
resolved "https://registry.yarnpkg.com/rechoir/-/rechoir-0.6.2.tgz#85204b54dba82d5742e28c96756ef43af50e3384"
integrity sha512-HFM8rkZ+i3zrV+4LQjwQ0W+ez98pApMGM3HUrN04j3CqzPOzl9nmP15Y8YXNm8QHGv/eacOVEjqhmWpkRV0NAw==
dependencies:
resolve "^1.1.6"
rechoir@^0.8.0: rechoir@^0.8.0:
version "0.8.0" version "0.8.0"
resolved "https://registry.yarnpkg.com/rechoir/-/rechoir-0.8.0.tgz#49f866e0d32146142da3ad8f0eff352b3215ff22" resolved "https://registry.yarnpkg.com/rechoir/-/rechoir-0.8.0.tgz#49f866e0d32146142da3ad8f0eff352b3215ff22"
@ -7675,7 +7663,7 @@ resolve.exports@^2.0.0:
resolved "https://registry.yarnpkg.com/resolve.exports/-/resolve.exports-2.0.2.tgz#f8c934b8e6a13f539e38b7098e2e36134f01e800" resolved "https://registry.yarnpkg.com/resolve.exports/-/resolve.exports-2.0.2.tgz#f8c934b8e6a13f539e38b7098e2e36134f01e800"
integrity sha512-X2UW6Nw3n/aMgDVy+0rSqgHlv39WZAlZrXCdnbyEiKm17DSqHX4MmQMaST3FbeWR5FTuRcUwYAziZajji0Y7mg== integrity sha512-X2UW6Nw3n/aMgDVy+0rSqgHlv39WZAlZrXCdnbyEiKm17DSqHX4MmQMaST3FbeWR5FTuRcUwYAziZajji0Y7mg==
resolve@^1.1.6, resolve@^1.11.1, resolve@^1.14.2, resolve@^1.19.0, resolve@^1.20.0: resolve@^1.11.1, resolve@^1.14.2, resolve@^1.19.0, resolve@^1.20.0:
version "1.22.8" version "1.22.8"
resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.22.8.tgz#b6c87a9f2aa06dfab52e3d70ac8cde321fa5a48d" resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.22.8.tgz#b6c87a9f2aa06dfab52e3d70ac8cde321fa5a48d"
integrity sha512-oKWePCxqpd6FlLvGV1VU0x7bkPmmCNolxzjMf4NczoDnQcIWrAF+cPtZn5i6n+RfD2d9i0tzpKnG6Yk168yIyw== integrity sha512-oKWePCxqpd6FlLvGV1VU0x7bkPmmCNolxzjMf4NczoDnQcIWrAF+cPtZn5i6n+RfD2d9i0tzpKnG6Yk168yIyw==
@ -7903,23 +7891,6 @@ shell-quote@^1.8.1:
resolved "https://registry.yarnpkg.com/shell-quote/-/shell-quote-1.8.1.tgz#6dbf4db75515ad5bac63b4f1894c3a154c766680" resolved "https://registry.yarnpkg.com/shell-quote/-/shell-quote-1.8.1.tgz#6dbf4db75515ad5bac63b4f1894c3a154c766680"
integrity sha512-6j1W9l1iAs/4xYBI1SYOVZyFcCis9b4KCLQ8fgAGG07QvzaRLVVRQvAy85yNmmZSjYjg4MWh4gNvlPujU/5LpA== integrity sha512-6j1W9l1iAs/4xYBI1SYOVZyFcCis9b4KCLQ8fgAGG07QvzaRLVVRQvAy85yNmmZSjYjg4MWh4gNvlPujU/5LpA==
shelljs@^0.8.5:
version "0.8.5"
resolved "https://registry.yarnpkg.com/shelljs/-/shelljs-0.8.5.tgz#de055408d8361bed66c669d2f000538ced8ee20c"
integrity sha512-TiwcRcrkhHvbrZbnRcFYMLl30Dfov3HKqzp5tO5b4pt6G/SezKcYhmDg15zXVBswHmctSAQKznqNW2LO5tTDow==
dependencies:
glob "^7.0.0"
interpret "^1.0.0"
rechoir "^0.6.2"
shx@^0.3.4:
version "0.3.4"
resolved "https://registry.yarnpkg.com/shx/-/shx-0.3.4.tgz#74289230b4b663979167f94e1935901406e40f02"
integrity sha512-N6A9MLVqjxZYcVn8hLmtneQWIJtp8IKzMP4eMnx+nqkvXoqinUPCbUFLp2UcWTEIUONhlk0ewxr/jaVGlc+J+g==
dependencies:
minimist "^1.2.3"
shelljs "^0.8.5"
side-channel@^1.0.4: side-channel@^1.0.4:
version "1.0.4" version "1.0.4"
resolved "https://registry.yarnpkg.com/side-channel/-/side-channel-1.0.4.tgz#efce5c8fdc104ee751b25c58d4290011fa5ea2cf" resolved "https://registry.yarnpkg.com/side-channel/-/side-channel-1.0.4.tgz#efce5c8fdc104ee751b25c58d4290011fa5ea2cf"