Compare commits

...

81 Commits

Author SHA1 Message Date
bobo.yang
0e864bf52d refactor: remove command prefixes from Chinese translation
- Removed redundant command prefixes like "/unit_tests", "/commit"
- Maintained corresponding Chinese translations for all commands
- Simplified the i18n file structure while preserving functionality
2025-03-12 13:16:40 +08:00
Jinglei Ren
37d745b806
Update issue templates 2025-02-27 12:12:16 +08:00
bobo.yang
d849bc4a33 chore: Add environment configuration and update gitignore
- Add initial .env file with app configuration settings
- Configure image base URL and assistant display names
- Remove .env from gitignore while keeping avatar file ignored
2025-02-24 14:11:49 +08:00
boob.yang
da327b8add
Merge pull request #97 from devchat-ai/feat/add-event-operation-data
feat: Add message tracking and event operation data
2024-12-31 10:11:23 +08:00
bobo.yang
0f45b9edc2 feat: Add message tracking and event operation data
- Add getCurrentMessageId and updateCurrentMessageId methods to APIUtil
- Update event creation to include message IDs across components
- Enhance event tracking with detailed value data in code operations
2024-12-31 09:51:15 +08:00
boob.yang
838fab9806
Merge pull request #96 from devchat-ai/refactor/a-link-processing
refactor: Update link handling in MessageMarkdown component
2024-12-29 20:26:46 +08:00
bobo.yang
fee77d07b7 refactor: Update link handling in MessageMarkdown component
- Replace anchor tag with custom Anchor component for external links
- Add click handler to process links through messageUtil command
- Implement openLink command for better link processing control
2024-12-29 20:24:06 +08:00
boob.yang
2f6f379e91
Merge pull request #95 from devchat-ai/feat/add-plugin-version-to-message-event
feat: Add plugin version to message and event data
2024-12-26 16:57:52 +08:00
bobo.yang
264555ceb8 feat: Add plugin version to message and event data
- Add extension version retrieval functionality via IDEServiceUtil
- Implement version info appending to message.ide and event.ide fields
- Add TypeScript interfaces for MessageData and EventData types
2024-12-26 16:56:51 +08:00
boob.yang
4a6cb2e7b3
Merge pull request #94 from devchat-ai/fix/message-send-focus
fix: Add focus and prevent default on message send
2024-12-02 16:47:16 +08:00
bobo.yang
74b9bbc3f9 fix: Add focus and prevent default on message send
- Add focus to input after sending message
- Prevent default behavior on Enter key press
- Improve user experience in message input component
2024-12-02 16:46:38 +08:00
bobo.yang
c8836777f0 fix: Handle string values in event creation
- Update value handling in APIUtil.createEvent
- Check if message.value is a string before stringifying
- Ensure compatibility with both string and object values
2024-11-26 17:40:01 +08:00
bobo.yang
66c9115f69 feat: Enhance InputMessage component behavior
- Add cursor reset functionality after sending message
- Prevent input during message generation
- Remove disabled state from textarea for better UX
2024-11-25 21:50:30 +08:00
bobo.yang
8f2f082849 update default model when it is invalid 2024-11-22 10:13:33 +08:00
bobo.yang
7103c33b80 fix call acquireIdeaCodeApi before it reged 2024-11-22 07:36:20 +08:00
bobo.yang
25f18c09f3 fix multiselect error 2024-11-15 16:06:20 +08:00
boob.yang
e53f2009eb
Merge pull request #93 from devchat-ai/feat/add-multi-select-dropdown
feat: Add multi-select dropdown component
2024-11-14 19:11:27 +08:00
bobo.yang
c67b694c4a feat: Add multi-select dropdown component
- Implement MultiSelect component in ChatMark
- Extend widget type to include "multiSelect" option
- Add handleSelectChange function for multi-select logic
2024-11-14 19:05:37 +08:00
boob.yang
1724b70904
Merge pull request #92 from devchat-ai/feat/selected-code-diff-button
feat: Add selected code support for diff button
2024-11-14 16:26:53 +08:00
bobo.yang
621ee29fff feat: Add selected code support for diff button
- Implement selection-aware diff functionality
- Use selected text for diff if available, else use full code
- Maintain existing event tracking and message sending logic
2024-11-14 16:25:29 +08:00
boob.yang
7152d8d28a
Merge pull request #91 from devchat-ai/feat/add-env-example
feat: Add .env.example file
2024-11-14 12:38:13 +08:00
bobo.yang
0cbb2e0fab feat: Add .env.example file
- Added .env.example with environment variables
- Set REACT_APP_IMAGE_BASE_URL and assistant display names
- Defined REACT_APP_LOGO_FILE variable
2024-11-14 12:37:31 +08:00
bobo.yang
3cbdf30bcd retry for logEvent 2024-11-14 08:51:22 +08:00
bobo.yang
7959e86f62 update logMessage logEvent api 2024-11-14 08:05:09 +08:00
bobo.yang
aae2f7b567 fix error in logMessage 2024-11-13 21:34:51 +08:00
bobo.yang
2b3cc544fd add message api to log message to server 2024-11-13 20:36:48 +08:00
boob.yang
dc4ac3bfa6
Merge pull request #90 from devchat-ai/webview-communication-vscode-idea
remove special process for idea communication
2024-11-13 20:23:57 +08:00
bobo.yang
8105498ae0 remove special process for idea communication 2024-11-13 20:22:53 +08:00
boob.yang
c011224587
Merge pull request #89 from devchat-ai/fix_install_error_on_windows
add shx, fix mv command error on windows
2024-11-05 11:25:20 +08:00
bobo.yang
297a333833 add shx, fix mv command error on windows 2024-11-05 11:24:14 +08:00
boob.yang
189d777643
Merge pull request #88 from devchat-ai/update_gitignore
update gitignore
2024-10-24 19:43:28 +08:00
bobo.yang
8f38c69487 update gitignore 2024-10-24 19:42:29 +08:00
boob.yang
9dd8270fb6
Merge pull request #87 from devchat-ai/remove_env_file
remove .env
2024-10-24 19:41:07 +08:00
bobo.yang
9f6ac03afe remove .env 2024-10-24 19:34:37 +08:00
Rankin Zheng
87fb8b5d9d
Merge pull request #86 from devchat-ai/auto_custom_workflow
refactor(i18n): update reload and sync text for clarity
2024-09-02 16:01:12 +08:00
Rankin Zheng
7df76004a4 refactor(i18n): update reload and sync text for clarity
Change the text for the "Reload" button to "Reload built-in & custom workflows" and add a
new "Sync settings from cloud" entry in the zh.json file to improve clarity on the functionality
each button provides. These changes make the user interface more intuitive for users.
2024-09-02 15:58:50 +08:00
Rankin Zheng
34f4b4fc8c
Merge pull request #85 from devchat-ai/auto_custom_workflow
Auto custom workflow
2024-09-02 14:53:44 +08:00
Rankin Zheng
66972a90f7 refactor(Config): update workflow list with settled state management
Refactor the workflow list update logic in the Config page to handle the loading state
more efficiently. Now, it waits for the workflow list update to complete before setting
the settled state to true and updating the route to "chat". This sequence ensures that
the loading indicator is not visible unnecessarily and improves the user experience.
2024-09-02 14:51:14 +08:00
Rankin Zheng
de280846e5 refactor(config): streamline ConfigStore methods and update Config component
Refactor ConfigStore by removing redundant 'updateSettle' method and streamline button
handlers in the Config component for better code maintainability. Additionally, consolidate
button functionality to avoid confusion.
2024-09-02 09:50:14 +08:00
Rankin Zheng
13886a05c7 fix(config): add reset form functionality to cancel button
Ensure the form resets when the cancel button is pressed, enhancing the user experience
and preventing unintended submissions with outdated data.\
2024-09-02 08:29:57 +08:00
Rankin Zheng
2499ff5312 refactor(updateWorkflowList): replace Local Service call with IDE Service
Update the workflow list retrieval mechanism by replacing the direct local service
invocation with a call to the IDE service. This change introduces a more structured
approach to interacting with services and sets the foundation for future service
communications within the application.
2024-08-28 06:50:14 +08:00
Rankin Zheng
56e30328be fix(App): remove outdated workflow list update calls
Removed the outdated calls to update the workflow list in both the App.tsx and ConfigStore.ts
files. This change streamlines the code and prevents the execution of redundant Local Service
commands that are no longer required, potentially improving performance and reducing unnecessary
network activity.
2024-08-27 18:32:16 +08:00
Rankin Zheng
87c6c104db refactor(config): add reload button and remove sync cloud config
- Implement a 'Reload' button to refresh the configuration.
- Remove the 'Sync Cloud Configs && Reload Workflows' button to simplify the UI.
- Adjust the 'Save' button to be conditionally displayed based on whether the form is being submitted.
2024-08-27 18:28:52 +08:00
Tim
d3ae07dc82
Merge pull request #84 from devchat-ai/parameterize-packaging
Parameterize packaging
2024-08-27 17:23:10 +08:00
Luo Tim
e1cc86ffa8 Clean up after build 2024-08-27 17:22:34 +08:00
Rankin Zheng
82873a0b8a feat(config): add button to sync configs and reload workflows
A new button has been introduced on the Config page to allow users to synchronise cloud
configs and reload workflows effortlessly. This addition streamlines the configuration and
workflow management process within the application.
2024-08-26 18:31:04 +08:00
Luo Tim
62be35f2bf Add README 2024-08-23 02:40:19 +08:00
Luo Tim
527bc77822 Add parameters env and prebuild script 2024-08-23 02:17:51 +08:00
Luo Tim
92da334aee make assistant name configurable 2024-08-23 02:16:50 +08:00
Tim
7c361f6039
Merge pull request #83 from devchat-ai/revert-82-customize-logo
Revert "Sync custom icons from server"
2024-08-20 17:33:09 +08:00
Tim
d762fac0d3
Revert "Sync custom icons from server" 2024-08-20 17:32:55 +08:00
Tim
26c7d79e9d
Merge pull request #82 from devchat-ai/customize-logo
Sync custom icons from server
2024-08-19 00:26:02 +08:00
Luo Tim
cb42bb0d42 Sync custom icons from server 2024-08-19 00:24:37 +08:00
boob.yang
e7dea52c58
Merge pull request #81 from devchat-ai/limit_apply_edit_action
feat: Limit EditApplyButton to 'edits' language
2024-07-18 17:07:08 +08:00
bobo
f4cf03c675 feat: Limit EditApplyButton to 'edits' language
- Restrict EditApplyButton rendering to 'edits' language only
- Wrap EditApplyButton in conditional statement
- Improve code clarity and functionality in CodeButtons component
2024-07-18 17:06:15 +08:00
Tim
d97b8a5bad
Merge pull request #80 from devchat-ai/add_edit_apply_button
feat: Add Edit and Apply button for code blocks
2024-07-08 10:13:42 +08:00
bobo
dcb63f6b52 Refactor EditApplyButton label to 'Apply Code' 2024-07-08 08:40:45 +08:00
bobo
6bcaee566c feat: Add Edit and Apply button for code blocks
- Implement new EditApplyButton component in CodeButtons
- Add callService method to IDEServiceUtil for API interactions
- Include EditApplyButton in CodeButtons component render
2024-07-04 12:55:59 +08:00
boob.yang
03089d049a
Merge pull request #79 from devchat-ai/add_logEvent
feat: Add logEvent handler in ideaBridge.ts
2024-06-25 15:06:38 +08:00
bobo
cd1ae1fcc4 feat: Add logEvent handler in ideaBridge.ts 2024-06-25 14:52:50 +08:00
Tim
758ae71aeb
Merge pull request #78 from devchat-ai/clean-up
Remove python for commands config
2024-06-20 08:46:46 +08:00
Luo Tim
9fa797ffd3 Remove python for commands config 2024-06-20 08:42:16 +08:00
Rankin Zheng
8d281973e2
Merge pull request #77 from devchat-ai/clean-up
Clean up
2024-06-19 17:21:11 +08:00
Luo Tim
146973cd5d Remove doCommit handler 2024-06-19 16:50:46 +08:00
Luo Tim
bb8b0d069b Add addContext/notify back 2024-06-19 16:45:52 +08:00
Tim
88e3673aef
Merge pull request #73 from devchat-ai/remove_unsed_codelines
Refactor bridge.md and ideaBridge.ts
2024-06-18 21:51:43 +08:00
boob.yang
3868618aa9
Merge pull request #76 from devchat-ai/fix_stream_config_error
feat: Add stream option to model configuration
2024-06-18 16:45:27 +08:00
bobo
ae797b3e21 feat: Add stream option to model configuration 2024-06-18 16:42:53 +08:00
Tim
e5c71c777c
Merge pull request #75 from devchat-ai/submit-and-cancel-events
feat: Integrate IDE event tracking in ChatMark component
2024-06-18 16:33:26 +08:00
Luo Tim
9ba9bd15a3 feat: Integrate IDE event tracking in ChatMark component
- Added IDEServiceUtil and APIUtil imports for event tracking
- Integrated submit and cancel event tracking with IDE context
- Determined platform environment and handled event data accordingly
2024-06-18 16:29:33 +08:00
boob.yang
a8d20e4cd5
Merge pull request #74 from devchat-ai/fix_chatWithDevChat_event
fix: Correct handling of chatWithDevChat event
2024-06-18 15:16:38 +08:00
bobo
9fcf526ec3 fix: Correct handling of chatWithDevChat event
- Updated InputMessage component to set input value and trigger send event
- Replaced chat.commonMessage call with direct input manipulation
- Modified event handling to ensure proper dispatch of chat messages
2024-06-18 15:13:59 +08:00
bobo
0232d4bc37 Refactor bridge.md and ideaBridge.ts 2024-06-17 21:34:39 +08:00
bobo
ed5b3ce84e Refactor bridge.md and ideaBridge.ts 2024-06-17 21:34:39 +08:00
bobo
eab0935dc2 Refactor bridge.md and ideaBridge.ts 2024-06-17 21:34:39 +08:00
boob.yang
d9703da439
Merge pull request #72 from devchat-ai/fix_default_model_change
Set Default Provider
2024-06-17 21:17:00 +08:00
bobo
17a04a31c1 set default provider 2024-06-17 21:15:01 +08:00
Tim
6a277c0759
Merge pull request #71 from devchat-ai/add-copy-language-platform-info
Add copy language platform info
2024-06-17 13:44:49 +08:00
Luo Tim
3d13e6c4c9 Add copy language platform info 2024-06-17 13:44:23 +08:00
Tim
e50b618da2
Merge pull request #70 from devchat-ai/extract-current-file-extension
Add ide and language info in code diff apply event
2024-06-17 13:32:58 +08:00
Tim
5b4fcf4010
Merge pull request #69 from devchat-ai/extract-current-file-extension
Extract current file extension from path
2024-06-17 13:21:48 +08:00
31 changed files with 709 additions and 925 deletions

5
.env
View File

@ -1 +1,4 @@
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=

4
.env.example Normal file
View File

@ -0,0 +1,4 @@
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=

38
.github/ISSUE_TEMPLATE/bug_report.md vendored Normal file
View File

@ -0,0 +1,38 @@
---
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

@ -0,0 +1,20 @@
---
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,3 +22,4 @@ dist-ssr
*.njsproj
*.sln
*.sw?
avatar_devchat.svg

34
README.md Normal file
View File

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

23
prebuild.js Normal file
View File

@ -0,0 +1,23 @@
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,5 +1,15 @@
import axios from "axios";
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 {
private static instance: APIUtil;
@ -7,6 +17,7 @@ class APIUtil {
private accessKey: string | undefined;
private webappUrl: string | undefined;
private currentMessageId: string | undefined;
private extensionVersion: string | undefined;
constructor() {
@ -19,6 +30,22 @@ class APIUtil {
}
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() {
try {
const res = await axios.get(
@ -58,38 +85,74 @@ class APIUtil {
})
}
async createMessage(message: object) {
updateCurrentMessageId() {
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 {
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(
`${this.webappUrl}/api/v1/messages`,
{...message, message_id: this.currentMessageId},
{...message, message_id: newMessageId},
{ headers: {
Authorization: `Bearer ${this.accessKey}`,
'Content-Type': 'application/json',
}}
)
);
console.log("Message created: ", res?.data);
} catch(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;
}
};
async createEvent(event: object) {
try {
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);
if (!(await attemptCreate())) {
await new Promise(resolve => setTimeout(resolve, 2000));
await attemptCreate();
}
}
@ -109,4 +172,4 @@ class APIUtil {
}
export default APIUtil.getInstance();
export default APIUtil.getInstance();

View File

@ -1,5 +1,10 @@
import axios from "axios";
interface ServiceResponse {
result?: any;
error?: string;
}
class IDEServiceUtil {
private static instance: IDEServiceUtil;
private host = "http://localhost";
@ -33,6 +38,35 @@ class IDEServiceUtil {
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;
}
}
}

View File

@ -1,28 +1,15 @@
import IdeaBridge from "./ideaBridge";
class MessageUtil {
private static instance: MessageUtil;
handlers: { [x: string]: any };
vscodeApi: any;
ideApi: any;
messageListener: any;
hasInit: boolean;
constructor() {
this.handlers = {};
this.messageListener = null;
if (process.env.platform === "vscode") {
this.vscodeApi = window.acquireVsCodeApi();
}
if (!this.messageListener) {
this.messageListener = (event: { data: any }) => {
const message = event.data;
this.handleMessage(message);
};
window.addEventListener("message", this.messageListener);
} else {
console.log("Message listener has already been bound.");
}
this.hasInit = false;
this.handlers = {};
this.messageListener = null;
}
public static getInstance(): MessageUtil {
@ -32,20 +19,43 @@ class MessageUtil {
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
registerHandler(messageType: string, handler: any) {
if (process.env.platform === "idea") {
IdeaBridge.registerHandler(messageType, handler);
} else {
if (!this.handlers[messageType]) {
this.init();
if (!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
unregisterHandler(messageType: string | number, handler: any) {
this.init();
if (this.handlers[messageType]) {
this.handlers[messageType] = this.handlers[messageType].filter(
(h: any) => h !== handler
@ -55,29 +65,23 @@ class MessageUtil {
// Handle a received message
handleMessage(message: { command: string | number } & Record<string, any>) {
this.init();
if (!('command' in message)) {
throw new Error('Missing required field: command');
}
if (process.env.platform === "idea") {
IdeaBridge.handleMessage(message)
} else {
const handlers = this.handlers[message.command];
if (handlers) {
const handlers = this.handlers[message.command];
if (handlers) {
handlers.forEach((handler: (arg0: { command: string | number } & Record<string, any>) => any) =>
handler(message)
handler(message)
);
}
}
}
// Send a message to the VSCode API
sendMessage(message: any) {
if (process.env.platform === "idea") {
IdeaBridge.sendMessage(message);
} else {
this.vscodeApi.postMessage(message);
}
this.init();
this.ideApi.postMessage(message);
}
}

View File

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

2
src/util/constants.ts Normal file
View File

@ -0,0 +1,2 @@
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"

View File

@ -1,690 +0,0 @@
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));
},
getIDEServicePort: () => {
// 获取完整的用户设置
const params = {
action: "getIDEServicePort/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));
},
readServerConfigBase: () => {
// 获取完整的用户设置
const params = {
action: "getServerSettings/request",
metadata: {
callback: "IdeaToJSMessage",
},
payload: {},
};
window.JSJavaBridge.callJava(JSON.stringify(params));
},
saveConfig: (data) => {
const params = {
action: "updateSetting/request",
metadata: {
callback: "IdeaToJSMessage",
},
payload: data,
};
window.JSJavaBridge.callJava(JSON.stringify(params));
},
writeServerConfigBase: (data) => {
const params = {
action: "updateServerSettings/request",
metadata: {
callback: "IdeaToJSMessage",
},
payload: data,
};
window.JSJavaBridge.callJava(JSON.stringify(params));
},
};
class IdeaBridge {
private static instance: IdeaBridge;
handle: any = {};
constructor() {
this.handle = {};
// 注册全局的回调函数,用于接收来自 IDEA 的消息
window.IdeaToJSMessage = (res: any) => {
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 "getServerSettings/response":
this.resviceServerSettings(res);
break;
case "listTopics/response":
this.resviceTopicList(res);
break;
case "loadConversations/response":
this.resviceTopicDetail(res);
break;
case "getIDEServicePort/response":
this.resviceIDEServicePort(res);
break;
default:
break;
}
};
window.onInitializationFinish = () => {
// 初始化完成
JStoIdea.getCommandList();
};
}
resviceUpdateSetting(res) {
// 更新用户设置之后的回调
this.executeHandlers("updateSetting", res.payload);
}
resviceCodeDiffApply(res) {
this.executeHandlers("codeDiffApply", res.payload);
}
resviceIDEServicePort(res) {
this.executeHandlers("getIDEServicePort", 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,
});
}
resviceServerSettings(res) {
// 获取用户设置的回调
const setting = res?.payload || {};
this.executeHandlers("readServerConfigBase", {
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);
});
}
}
handleMessage(message: { command: string | number } & Record<string, any>) {
if (!('command' in message)) {
throw new Error('Missing required field: command');
}
const messageType = message.command
if (this.handle[messageType]) {
this.handle[messageType].forEach((handler) => {
handler(message);
});
}
}
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 "readServerConfigBase":
JStoIdea.readServerConfigBase();
break;
case "writeConfig":
// 保存用户设置
JStoIdea.saveConfig(message.value);
break;
case "writeServerConfigBase":
// 保存用户设置
JStoIdea.writeServerConfigBase(message.value);
break;
case "getIDEServicePort":
// 保存用户设置
JStoIdea.getIDEServicePort();
break;
default:
break;
}
}
}
export default IdeaBridge.getInstance();

View File

@ -28,10 +28,25 @@ export default function App() {
const getConfig = () => {
// 比较函数
const compare_func = (configs: any) => {
const [serverConfig, userConfig] = config.updateConfig(configs.server_config, configs.server_config_base, configs.user_config);
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 });
// 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
@ -54,14 +69,17 @@ export default function App() {
const checkConfigs = () => {
if (configCount === 3) {
compare_func(configs);
setReady(true); // 假设setReady是一个函数用于设置应用状态为准备就绪
// 假设setReady是一个函数用于设置应用状态为准备就绪
setReady(true);
// update workflow list
config.updateWorkflowList();
}
};
// 注册处理函数
MessageUtil.registerHandler("readConfig", (data: { value: any }) => {
config.setConfig(data.value);
APIUtil.config(config.getAPIBase(), config.getUserKey())
APIUtil.config(config.getAPIBase(), config.getUserKey());
setReady(true);
config.fetchServerConfig();
handleConfig("user_config", data);

View File

@ -7,10 +7,13 @@ import {
Radio,
Textarea,
createStyles,
MultiSelect
} from "@mantine/core";
import { useListState, useSetState } from "@mantine/hooks";
import { useMst } from "@/views/stores/RootStore";
import yaml from "js-yaml";
import IDEServiceUtil from "@/util/IDEServiceUtil";
import APIUtil from "@/util/APIUtil";
const useStyles = createStyles((theme) => ({
container: {
@ -56,7 +59,7 @@ interface IWdiget {
id: string;
value: string;
title?: string;
type: "editor" | "checkbox" | "radio" | "button" | "text";
type: "editor" | "checkbox" | "radio" | "button" | "text"| "multiSelect";
submit?: string;
cancel?: string;
}
@ -75,6 +78,7 @@ const ChatMark = ({
const values = value ? yaml.load(value) : {};
const [disabled, setDisabled] = useState(messageDone || !!value);
const [checkboxArray, setcheckboxArray] = useState<any>([]);
const platform = process.env.platform === "idea" ? "intellij" : process.env.platform;
const handleSubmit = () => {
let formData = {};
@ -91,12 +95,24 @@ const ChatMark = ({
formData[widget.id] = widget.value;
});
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 = () => {
chat.userInput({
form: "canceled",
});
IDEServiceUtil.getCurrentFileInfo().then(info => APIUtil.createEvent({
name: "cancel",
ide: platform,
language: info?.extension || info?.path?.split('.').pop()
}));
};
const handleButtonClick = ({ event, index }) => {
@ -152,6 +168,23 @@ const ChatMark = ({
widget["value"] = event.currentTarget.value;
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(() => {
const lines = children.split("\n");
@ -161,6 +194,7 @@ const ChatMark = ({
const textRegex = /^([^>].*)/; // Text widget
const buttonRegex = /^>\s*\((.*?)\)\s*(.*)/; // Button widget
const checkboxRegex = /^>\s*\[([x ]*)\]\((.*?)\)\s*(.*)/; // Checkbox widget
const selectRegex = /^>\s*\{([x ]*)\}\((.*?)\)\s*(.*)/; //MultiSelect widget
const radioRegex = /^>\s*-\s*\((.*?)\)\s*(.*)/; // Radio button widget
const editorRegex = /^>\s*\|\s*\((.*?)\)/; // Editor widget
const editorContentRegex = /^>\s*(.*)/; // Editor widget
@ -202,6 +236,40 @@ const ChatMark = ({
? 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) {
currentCheckboxData.group.push({
id: id,
@ -289,6 +357,9 @@ const ChatMark = ({
let radioValuesTemp: any = [];
let wdigetsTemp: any = [];
let prevIsCheckbox = false;
let groupTemp: any = [];
let valuesTemp: any = [];
let selectValues: any = [];
widgets.map((widget, index) => {
if (widget.type === "text") {
wdigetsTemp.push(<Text key={index}>{widget.value}</Text>);
@ -406,6 +477,34 @@ const ChatMark = ({
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";

View File

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

View File

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

View File

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

View File

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

View File

@ -1,10 +1,11 @@
import { Tooltip, ActionIcon, CopyButton, Flex } from "@mantine/core";
import { IconCheck, IconGitCommit, IconFileDiff, IconColumnInsertRight, IconReplace, IconCopy,IconFile } from "@tabler/icons-react";
import { IconCheck, IconGitCommit, IconFileDiff, IconEdit, IconColumnInsertRight, IconReplace, IconCopy,IconFile } from "@tabler/icons-react";
import React, { useState } from "react";
import { useMst } from "@/views/stores/RootStore";
import messageUtil from '@/util/MessageUtil';
import APIUtil from "@/util/APIUtil";
import IDEServiceUtil from "@/util/IDEServiceUtil";
const IconButton = ({ label, color = 'gray', onClick, children }) => (
<Tooltip sx={{ padding: '3px', fontSize: 'var(--vscode-editor-font-size)' }} label={label} withArrow position="left" color="gray">
@ -14,30 +15,16 @@ const IconButton = ({ label, color = 'gray', onClick, children }) => (
</Tooltip>
);
const CommitButton = ({ code }) => {
const [commited, setCommited] = useState(false);
const handleClick = () => {
messageUtil.sendMessage({
command: 'doCommit',
content: code
});
setCommited(true);
setTimeout(() => { setCommited(false); }, 2000);
};
return (
<IconButton label={commited ? 'Committing' : 'Commit'} color={commited ? 'teal' : 'gray'} onClick={handleClick}>
{commited ? <IconCheck size="1rem" /> : <IconGitCommit size="1rem" />}
</IconButton>
);
};
const CodeCopyButton = ({ code }) => {
const CodeCopyButton = ({ code, language, platform }) => {
return (
<CopyButton value={code} timeout={2000}>
{({ copied, copy }) => (
<IconButton label={copied ? 'Copied' : 'Copy'} color={copied ? 'teal' : 'gray'} onClick={() => {
copy();
APIUtil.createEvent({name: 'copy', value: 'copy'})
APIUtil.createEvent(
{name: 'copy', value: code, language: language, ide: platform},
APIUtil.getCurrentMessageId()
);
}}>
{copied ? <IconCheck size="1rem" /> : <IconCopy size="1rem" />}
</IconButton>
@ -49,12 +36,27 @@ const CodeCopyButton = ({ code }) => {
const DiffButton = ({ code, language, platform }) => {
const handleClick = () => {
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({
command: e,
content: code
content: selectedCode
});
APIUtil.createEvent({name: e, value: e, language: language, ide: platform})
APIUtil.createEvent(
{name: e, value: selectedCode, language: language, ide: platform},
APIUtil.getCurrentMessageId()
);
};
return (
<IconButton label='View Diff' onClick={handleClick}>
<IconFileDiff size="1.125rem" />
@ -62,6 +64,25 @@ const DiffButton = ({ code, language, platform }) => {
);
};
const EditApplyButton = ({ code, language, platform }) => {
const handleClick = () => {
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 e = 'code_apply';
@ -69,7 +90,10 @@ const CodeApplyButton = ({ code, language, platform }) => {
command: e,
content: code
});
APIUtil.createEvent({name: e, value: e, language: language, ide: platform})
APIUtil.createEvent(
{name: e, value: code, language: language, ide: platform},
APIUtil.getCurrentMessageId()
);
};
return (
<IconButton label='Insert Code' onClick={handleClick}>
@ -85,7 +109,10 @@ const FileApplyButton = ({ code, language, platform }) => {
command: e,
content: code
});
APIUtil.createEvent({name: e, value: e, language: language, ide: platform})
APIUtil.createEvent(
{name: e, value: code, language: language, ide: platform},
APIUtil.getCurrentMessageId()
);
};
return (
<IconButton label='Replace File' onClick={handleClick}>
@ -103,7 +130,10 @@ const NewFileButton = ({ code, language, platform }) => {
language: language,
content: code
});
APIUtil.createEvent({name: e, value: e, language: language, ide: platform})
APIUtil.createEvent(
{name: e, value: code, language: language, ide: platform},
APIUtil.getCurrentMessageId()
);
};
return (
<IconButton label='Create New File' onClick={handleClick}>
@ -122,17 +152,16 @@ const CodeButtons = ({ platform, language, code }) => (
wrap="wrap"
style={{ position: 'absolute', top: 8, right: 10 }}
>
<CodeCopyButton code={code} />
{language && language === 'commitmsg'
? <CommitButton code={code} />
: (
<>
<DiffButton code={code} language={language} platform={platform} />
<CodeApplyButton code={code} language={language} platform={platform} />
<FileApplyButton code={code} language={language} platform={platform} />
<NewFileButton code={code} language={language} platform={platform} />
</>
<CodeCopyButton code={code} language={language} platform={platform} />
<>
<DiffButton code={code} language={language} platform={platform} />
{language === 'edits' && (
<EditApplyButton code={code} language={language} platform={platform} />
)}
<CodeApplyButton code={code} language={language} platform={platform} />
<FileApplyButton code={code} language={language} platform={platform} />
<NewFileButton code={code} language={language} platform={platform} />
</>
</Flex>
);

View File

@ -18,6 +18,7 @@ import { useTranslation } from "react-i18next";
import { useRouter } from "@/views/router";
import remarkGfm from "remark-gfm";
import APIUtil from "@/util/APIUtil";
import { ASSISTANT_DISPLAY_NAME } from "@/util/constants";
(typeof global !== "undefined" ? global : window).Prism = Prism;
require("prismjs/components/prism-java");
@ -112,8 +113,11 @@ const MessageMarkdown = observer((props: MessageMarkdownProps) => {
const selection = window.getSelection()?.toString();
console.log("Copied: ", selection);
const e = 'manual_copy';
APIUtil.createEvent({name: e, value: selection, language: language, ide: platform})
}
APIUtil.createEvent(
{name: e, value: selection, language: language, ide: platform},
APIUtil.getCurrentMessageId()
);
};
useEffect(() => {
let previousNode: any = null;
@ -168,7 +172,7 @@ const MessageMarkdown = observer((props: MessageMarkdownProps) => {
"Do you want to write some code or have a question about the project? "
)
) {
return t("devchat.help") + chat.helpWorkflowCommands();
return t("devchat.help", {assistantName: t(ASSISTANT_DISPLAY_NAME)}) + chat.helpWorkflowCommands();
}
if (
children.includes(
@ -176,23 +180,23 @@ const MessageMarkdown = observer((props: MessageMarkdownProps) => {
)
) {
if (process.env.platform === "vscode") {
return t("devchat.setkey_vscode");
return t("devchat.setkey_vscode", {assistantName: t(ASSISTANT_DISPLAY_NAME)});
}
return t("devchat.setkey");
return t("devchat.setkey", {assistantName: t(ASSISTANT_DISPLAY_NAME)});
}
if (
children.includes(
"DevChat intelligently navigates your codebase using GPT-4."
)
) {
return t("ask-code-explain");
return t("ask-code-explain", {assistantName: t(ASSISTANT_DISPLAY_NAME)});
}
if (
children.includes(
"Use this DevChat workflow to request code writing. Please input your specific requirement"
)
) {
return t("code-explain");
return t("code-explain", {assistantName: t(ASSISTANT_DISPLAY_NAME)});
}
if (
children.includes(
@ -406,9 +410,20 @@ const MessageMarkdown = observer((props: MessageMarkdownProps) => {
{children}
</Anchor>
) : (
<a {...props} href={href} className={className}>
<Anchor
className={classes.link}
href="javascript:void()"
onClick={() => {
if (href) {
messageUtil.sendMessage({
command: "openLink",
url: href,
});
}
}}
>
{children}
</a>
</Anchor>
);
},
img({ node, ...props }) {

View File

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

View File

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

View File

@ -54,12 +54,6 @@ const chatPanel = observer(() => {
behavior: "smooth",
});
const getFeatureToggles = () => {
messageUtil.sendMessage({
command: "featureToggles",
});
};
const timer = useTimeout(() => {
if (chat.isBottom) {
scrollToBottom();
@ -115,8 +109,6 @@ const chatPanel = observer(() => {
});
});
chat.fetchHistoryMessages();
input.fetchContextMenus().then();
getFeatureToggles();
}
);
messageUtil.registerHandler(
@ -150,26 +142,47 @@ const chatPanel = observer(() => {
input.clearContexts();
}
);
messageUtil.registerHandler(
"featureToggles",
(message: { features: object }) => {
chat.updateFeatures(message.features);
}
);
messageUtil.registerHandler("codeDiffApply", (_: any) => {
const e = 'code_diff_apply'
const e = 'code_diff_apply';
const platform = process.env.platform === "idea" ? "intellij" : process.env.platform;
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)
})
IDEServiceUtil.config(data.result);
});
messageUtil.sendMessage({ command: "getIDEServicePort" });
messageUtil.sendMessage({ command: "regCommandList" });

View File

@ -25,6 +25,7 @@ import cloneDeep from "lodash.clonedeep";
import { useTranslation } from "react-i18next";
import { useDisclosure } from "@mantine/hooks";
import { observer } from "mobx-react-lite";
import { ASSISTANT_DISPLAY_NAME } from "@/util/constants";
const commonInputStyle = {
label: {
@ -160,6 +161,24 @@ const Config = observer(() => {
}, 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 newModel = { ...form.values.models[current], [key]: value };
form.setFieldValue("models", {
@ -218,7 +237,7 @@ const Config = observer(() => {
>
<Stack>
<Tabs
defaultValue="Devchat"
defaultValue={ASSISTANT_DISPLAY_NAME}
variant="outline"
sx={{
".mantine-UnstyledButton-root::before": {
@ -228,12 +247,12 @@ const Config = observer(() => {
>
<Tabs.List>
<Tabs.Tab
value="Devchat"
value={ASSISTANT_DISPLAY_NAME}
sx={{
color: "var(--vscode-editor-foreground)",
}}
>
Devchat
{ASSISTANT_DISPLAY_NAME}
</Tabs.Tab>
<Tabs.Tab
value="OpenAI"
@ -245,7 +264,7 @@ const Config = observer(() => {
</Tabs.Tab>
</Tabs.List>
<Tabs.Panel
value="Devchat"
value={ASSISTANT_DISPLAY_NAME}
pt="xs"
p={10}
sx={{
@ -262,7 +281,7 @@ const Config = observer(() => {
...selectStyle,
}}
placeholder="https://xxxx.xx"
label={t("API Base of Devchat")}
label={t("config.api_base", {assistantName: t(ASSISTANT_DISPLAY_NAME)})}
withAsterisk
description={t("the base URL for the API")}
{...form.getInputProps("providers.devchat.api_base")}
@ -270,7 +289,7 @@ const Config = observer(() => {
{form.values.providers?.devchat?.api_base === "custom" && (
<TextInput
styles={commonInputStyle}
label={t("Custom API Base of Devchat")}
label={t("config.custom_api_base", {assistantName: t(ASSISTANT_DISPLAY_NAME)})}
withAsterisk
description={t("the base URL for the API")}
{...form.getInputProps(
@ -288,7 +307,7 @@ const Config = observer(() => {
},
}}
withAsterisk
label={t("Access Key of Devchat")}
label={t("config.access_key", {assistantName: t(ASSISTANT_DISPLAY_NAME)})}
placeholder={t("Your Access Key")}
description={t("please keep this secret")}
{...form.getInputProps("providers.devchat.api_key")}
@ -376,7 +395,7 @@ const Config = observer(() => {
...selectStyle,
}}
data={[
{ value: "devchat", label: "Devchat" },
{ value: "devchat", label: ASSISTANT_DISPLAY_NAME },
{ value: "openai", label: "OpenAI" },
]}
value={form.values?.models[current]?.provider}
@ -412,13 +431,6 @@ const Config = observer(() => {
description={t("Please enter the path of your python")}
{...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
styles={commonInputStyle}
label={t("Proxy setting")}
@ -458,6 +470,18 @@ 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>
<Group
grow
@ -478,6 +502,7 @@ const Config = observer(() => {
variant="outline"
color="gray"
onClick={() => {
form.reset();
router.updateRoute("chat");
}}
>

View File

@ -6,6 +6,8 @@ import { RootInstance } from "./RootStore";
import i18next from "i18next";
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 {
content: string;
@ -99,7 +101,6 @@ export const ChatStore = types
chatPanelWidth: 300,
disabled: false,
rechargeSite: "https://web.devchat.ai/pricing/",
features: types.optional(types.frozen(), {}),
key: types.optional(types.string, ""),
})
.actions((self) => {
@ -186,18 +187,19 @@ export const ChatStore = types
const supportedCommands = new Set(rootStore.input.commandMenus.map(x => x.name));
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) => {
let helps = `
Do you want to write some code or have a question about the project? Simply right-click on your chosen files or code snippets and add them to DevChat. Feel free to ask me anything or let me help you with coding.
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.
To see a list of workflows you can run in the context, just type "/". Happy prompting!
@ -206,17 +208,17 @@ To get started, here are some of the things that I can do for you:
${helpWorkflowCommands()}`;
const setKeyMessage = `
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.
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.
<button value="get_devchat_key" ${
process.env.platform === "vscode"
? 'href="https://web.devchat.ai" component="a"'
: ""
}>Get DevChat key</button>
<button value="setting_devchat_key">Set DevChat key</button>
}>Get ${ASSISTANT_DISPLAY_NAME} key</button>
<button value="setting_devchat_key">Set ${ASSISTANT_DISPLAY_NAME} key</button>
`;
const setKeyUser = `Is DevChat Access Key ready?`;
const setKeyUser = `Is ${ASSISTANT_DISPLAY_NAME} Access Key ready?`;
const accessKey = getParent<RootInstance>(self).config.getUserKey();
@ -237,7 +239,7 @@ Your DevChat Access Key is not detected in the current settings. Please set your
self.messages.push(
Message.create({
type: "user",
message: originalMessage ? "How do I use DevChat?" : "/help",
message: originalMessage ? i18next.t("devchat.help_question", {assistantName: i18next.t(ASSISTANT_DISPLAY_NAME)}) : "/help",
})
);
self.messages.push(
@ -355,9 +357,6 @@ Thinking...
const rootStore = getParent<RootInstance>(self);
rootStore.config.setConfigValue("default_model", chatModel);
},
updateFeatures: (features: any) => {
self.features = features;
},
startSystemMessage: () => {
self.generating = true;
self.responsed = false;
@ -395,6 +394,13 @@ Thinking...
} else {
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) => {

View File

@ -1,4 +1,5 @@
import MessageUtil from "@/util/MessageUtil";
import IDEServiceUtil from "@/util/IDEServiceUtil";
import { types, Instance, flow } from "mobx-state-tree";
import modelsTemplate from "@/models";
import cloneDeep from "lodash.clonedeep";
@ -26,6 +27,27 @@ function deepCopy(obj) {
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`, {
@ -115,12 +137,13 @@ export const ConfigStore = types
.filter((item) => item.category === "chat");
self.modelsTemplate = models;
};
const updateSettle = (value: boolean) => {
self.settle = value;
};
return {
setTemplate,
updateSettle: (value: boolean) => {
self.settle = value;
},
updateSettle,
getDefaultModel: () => {
return self.defaultModel;
},
@ -164,6 +187,8 @@ export const ConfigStore = types
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) => {
@ -173,6 +198,8 @@ export const ConfigStore = types
modelConfig[key] = model[key];
}
}
modelConfig["provider"] = provider;
modelConfig["stream"] = true;
localConfig["models"][model.model || model.id] = modelConfig;
});
@ -291,6 +318,9 @@ export const ConfigStore = types
MessageUtil.handleMessage({ command: "readServerConfig", value: undefined });
}
}),
updateWorkflowList: flow(function* (){
yield doUpdateWorkflowList();
}),
checkAndSetCompletionDefaults: (newConfig) => {
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;

View File

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

View File

@ -4377,7 +4377,7 @@ glob@7.2.0:
once "^1.3.0"
path-is-absolute "^1.0.0"
glob@^7.0.3, glob@^7.1.3, glob@^7.1.4:
glob@^7.0.0, glob@^7.0.3, glob@^7.1.3, glob@^7.1.4:
version "7.2.3"
resolved "https://registry.yarnpkg.com/glob/-/glob-7.2.3.tgz#b8df0fb802bbfa8e89bd1d938b4e16578ed44f2b"
integrity sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==
@ -4812,6 +4812,11 @@ inline-style-parser@0.1.1:
resolved "https://registry.yarnpkg.com/inline-style-parser/-/inline-style-parser-0.1.1.tgz#ec8a3b429274e9c0a1f1c4ffa9453a7fef72cea1"
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:
version "3.1.1"
resolved "https://registry.yarnpkg.com/interpret/-/interpret-3.1.1.tgz#5be0ceed67ca79c6c4bc5cf0d7ee843dcea110c4"
@ -6502,7 +6507,7 @@ minimatch@^5.0.1:
dependencies:
brace-expansion "^2.0.1"
minimist@^1.2.6:
minimist@^1.2.3, minimist@^1.2.6:
version "1.2.8"
resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.8.tgz#c1a464e7693302e082a075cee0c057741ac4772c"
integrity sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==
@ -7513,6 +7518,13 @@ readdirp@~3.6.0:
dependencies:
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:
version "0.8.0"
resolved "https://registry.yarnpkg.com/rechoir/-/rechoir-0.8.0.tgz#49f866e0d32146142da3ad8f0eff352b3215ff22"
@ -7663,7 +7675,7 @@ resolve.exports@^2.0.0:
resolved "https://registry.yarnpkg.com/resolve.exports/-/resolve.exports-2.0.2.tgz#f8c934b8e6a13f539e38b7098e2e36134f01e800"
integrity sha512-X2UW6Nw3n/aMgDVy+0rSqgHlv39WZAlZrXCdnbyEiKm17DSqHX4MmQMaST3FbeWR5FTuRcUwYAziZajji0Y7mg==
resolve@^1.11.1, resolve@^1.14.2, resolve@^1.19.0, resolve@^1.20.0:
resolve@^1.1.6, resolve@^1.11.1, resolve@^1.14.2, resolve@^1.19.0, resolve@^1.20.0:
version "1.22.8"
resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.22.8.tgz#b6c87a9f2aa06dfab52e3d70ac8cde321fa5a48d"
integrity sha512-oKWePCxqpd6FlLvGV1VU0x7bkPmmCNolxzjMf4NczoDnQcIWrAF+cPtZn5i6n+RfD2d9i0tzpKnG6Yk168yIyw==
@ -7891,6 +7903,23 @@ shell-quote@^1.8.1:
resolved "https://registry.yarnpkg.com/shell-quote/-/shell-quote-1.8.1.tgz#6dbf4db75515ad5bac63b4f1894c3a154c766680"
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:
version "1.0.4"
resolved "https://registry.yarnpkg.com/side-channel/-/side-channel-1.0.4.tgz#efce5c8fdc104ee751b25c58d4290011fa5ea2cf"