# 前言 {#前言}
我平常是一个比较容易纠结的人,每次遇到那种比较难取的方法名或类名的时候就会很纠结,于是一个IDEA插件的思路就出来了。做一个取名神器的插件,能很完美解决我现在遇到的问题。
项目的git地址如下:https://github.com/OliverLiy/NameGenius (opens new window)
# 效果演示 {#效果演示}
本插件基于开源大模型+Ollama部署框架实现,因此首先需要在设置中配置对应的API地址
插件的效果是这样的,你只需要在不知道如何取的方法名和属性名上写上中文,然后直接右键选择Name Genius
接着选择要转换的类名是方法名和类名
点击之后就会变成对应的英文名称了。
# 大模型部署教程 {#大模型部署教程}
因为基于开源的大模型实现了翻译的功能,因此首先演示如何在本地部署一个大模型。我因为本地就一台mac笔记本,大模型我都是租云算力的:https://gpuez.com/ (opens new window),有多种服务器资源可供选择,另外近期也有活动:8.1-10.31日,平台所有用户消费打八折,有需要的可以看看。
使用ollama这个开源框架实现大模型的快速部署,这是ollama的地址:https://github.com/ollama/ollama (opens new window)
首先执行第一行命令安装ollama
curl -fsSL https://ollama.com/install.sh | sh
接着设置一下环境变量使得所有ip都可以访问
export OLLAMA_HOST=0.0.0.0
然后开启ollama的服务
nohup ollama serve &
接着启动一个大模型,我这里就用llama3来完成这个插件
ollama run llama3
然后就可以通过ollama的11434端口进行api的调用了。
我用的是云算力,通过代理映射的方式将11434端口映射到了这个域名上,现在就可以在任何地方AI的接口了。
# 如何调教AI {#如何调教ai}
因为这个翻译的功能最终只希望让他返回方法名或类型,因此在prompt的描述上需要做一些控制,比如有一个"楼间距"的变量名称需要取,接口入参就可以这样配:
{
"model": "llama3",
"stream": false,
"messages": [
{
"role": "system",
"content": "你是一个Java的变量名翻译机器人,你只需要将接收到的中文翻译为对应的Java的变量名。不需要添加任何其他的说明文字及字符"
},
{
"role": "user",
"content": "楼间距"
}
]
}
得到的结果就是下面这样,message中的content就是返回的具体内容
{
"model": "llama3",
"created_at": "2024-07-21T10:05:46.92441256Z",
"message": {
"role": "assistant",
"content": "floorSpacing"
},
"done_reason": "stop",
"done": true,
"total_duration": 228328484,
"load_duration": 43350040,
"prompt_eval_count": 59,
"prompt_eval_duration": 25422000,
"eval_count": 3,
"eval_duration": 27690000
}
# 开始写插件 {#开始写插件}
# 编写配置页面 {#编写配置页面}
这已经是插件开发系列的第三个插件案例了,如果这个系列前面几期都有看的话,对这个插件的开发应该已经熟门熟路了。首先编写一个配置页面,用户配置ollama的api接口地址:
/**
* @author by: 神秘的鱼仔
* @ClassName: ApiSettingState
* @Description: Api配置的持久化类
* @Date: 2024/7/20 下午11:53
*/
@State(
name = "com.codeease.name.genius.window.ApiSettingState",
storages = @Storage("ApiSettingState.xml")
)
public class ApiSettingState implements PersistentStateComponent<ApiSettingState> {
public String api = "";
public static ApiSettingState getInstance(){
return ServiceManager.getService(ApiSettingState.class);
}
@Nullable
@Override
public ApiSettingState getState() {
return this;
}
@Override
public void loadState(@NotNull ApiSettingState apiSettingState) {
this.api = apiSettingState.api;
}
}
/**
* @author by: 神秘的鱼仔
* @ClassName: ApiSettingsComponent
* @Description: 设置组件类
* @Date: 2024/7/20 下午11:56
*/
public class ApiSettingsComponent {
private JPanel panel;
private JTextField apiTestField;
public ApiSettingsComponent() {
panel = new JPanel();
apiTestField = new JTextField(40);
panel.add(new JLabel("请输入Ollama的Api:"));
panel.add(apiTestField);
}
public JPanel getPanel() {
return panel;
}
public String getApi() {
return apiTestField.getText();
}
public void setApi(String api) {
apiTestField.setText(api);
}
}
/**
* @author by: 神秘的鱼仔
* @ClassName: ApiConfigurable
* @Description: API配置的最终实现
* @Date: 2024/7/20 下午11:57
*/
public class ApiConfigurable implements Configurable {
private ApiSettingsComponent settingsComponent;
@Override
public @Nls(capitalization = Nls.Capitalization.Title) String getDisplayName() {
return "Name Genius";
}
@Override
public @Nullable
JComponent createComponent() {
settingsComponent = new ApiSettingsComponent();
return settingsComponent.getPanel();
}
@Override
public boolean isModified() {
ApiSettingState settings = ApiSettingState.getInstance();
return !settingsComponent.getApi().equals(settings.api);
}
@Override
public void apply() {
ApiSettingState settings = ApiSettingState.getInstance();
settings.api = settingsComponent.getApi();
}
@Override
public void reset() {
ApiSettingState settings = ApiSettingState.getInstance();
settingsComponent.setApi(settings.api);
}
}
这样一个简单的配置页面就完成了。
# 编写Action按钮 {#编写action按钮}
这个功能要实现的内容是右键之后的选择以及中文内容的替换。
点击按钮后在页面中间跳出一个列表框。
/**
* @author by: 神秘的鱼仔
* @ClassName: GenerateNameAction
* @Description:
* @Date: 2024/7/18 下午3:45
*/
public class GenerateNameAction extends AnAction {
@Override
public void actionPerformed(@NotNull AnActionEvent event) {
Editor editor = (Editor) event.getDataContext().getData("editor");
Project project = event.getProject();
// 创建要展示的列表数据
List<String> typeNameList = NameTypeEnum.getTypeNameList();
// 创建列表弹出窗口
ListPopup listPopup = JBPopupFactory.getInstance()
.createListPopup(new NamePopupExecutor("Name Type",typeNameList,editor,project));
// 在屏幕中间显示列表弹出窗口
listPopup.showCenteredInCurrentWindow(Objects.requireNonNull(event.getProject()));
}
}
当点击列表框内的元素时,执行对应的替换逻辑
/**
* @author by: 神秘的鱼仔
* @ClassName: NamePopupExecutor
* @Description:
* @Date: 2024/7/18 下午4:15
*/
public class NamePopupExecutor extends BaseListPopupStep<String> {
private Editor editor;
private Project project;
public NamePopupExecutor(@NotNull String title, @NotNull List<String> values, Editor editor, Project project) {
super(title, values);
this.editor = editor;
this.project = project;
}
@Nullable
@Override
public PopupStep onChosen(String selectedValue, boolean finalChoice) {
// 处理选中的值
if (StringUtils.isNotBlank(selectedValue)) {
String selectedText = editor.getSelectionModel().getSelectedText();
if (StringUtils.isNotBlank(selectedText)){
NameConvertStrategy strategyInstance = NameTypeEnum.getStrategyInstance(selectedValue);
ReplaceProcess.replaceText(strategyInstance.execute(selectedText), editor, project);
}
}
// 如果是最终选择,则关闭弹出窗口
return finalChoice ? PopupStep.FINAL_CHOICE : super.onChosen(selectedValue, finalChoice);
}
@Override
public boolean hasSubstep(@Nullable String selectedValue) {
// 在这里可以定义是否有子步骤
return false;
}
@Nullable
@Override
public String getTextFor(String value) {
// 返回列表项的显示文本
return value;
}
@Nullable
@Override
public Icon getIconFor(String value) {
// 返回列表项的图标,如果不需要图标,则返回 null
return null;
}
}
以属性名生成的为例,实现的效果就是,传入的是中文的msg,返回的是AI给出的英文结果。
/**
* @author by: 神秘的鱼仔
* @ClassName: FieldNameConvert
* @Description: 属性名转换
* @Date: 2024/7/21 下午5:54
*/
public class FieldNameConvert implements NameConvertStrategy{
@Override
public String execute(String msg) {
String prompt = "你是一个Java的变量名翻译机器人,你只需要将接收到的中文翻译为对应的Java的变量名,而不需要其他任何修饰词";
AiExecutor aiExecutor = new AiExecutor();
return aiExecutor.getMethodNameByOllama(msg,prompt);
}
}
AiExecutor是调用ollama接口的执行器,代码如下:
/**
* @author by: 神秘的鱼仔
* @ClassName: AiExecutor
* @Description: 和AI相关的功能
* @Date: 2024/7/20 下午11:51
*/
public class AiExecutor {
public String getMethodNameByOllama(String name,String prompt){
OkHttpClient client = OkHttpClientSingleton.getInstance();
ChatRoleModel chatRoleModel = new ChatRoleModel();
chatRoleModel.setModel(LLAMA);
chatRoleModel.setStream(false);
chatRoleModel.setMessages(buildRoleContentModel(name,prompt));
String api = ApiSettingState.getInstance().api;
RequestBody requestBody = RequestBody.create(MediaType.parse("application/json"), JSON.toJSONString(chatRoleModel));
Request request = new Request.Builder()
.url(api)
.post(requestBody)
.build();
try (Response response = client.newCall(request).execute()) {
// 处理响应
if (response.isSuccessful()) {
ResponseBody responseBody = response.body();
String string = responseBody.string();
JSONObject jsonObject = JSON.parseObject(string);
JSONObject messageJson = jsonObject.getJSONObject(MESSAGE);
return messageJson.getString(CONTENT);
} else {
// 请求失败
System.out.println("请求失败,响应码: " + response.code());
return "请求失败,响应码: " + response.code();
}
} catch (IOException e) {
// 发生异常
e.printStackTrace();
}
return "请求失败";
}
public static List<RoleContentModel> buildRoleContentModel(String name,String prompt){
List<RoleContentModel> modelList = new ArrayList<>(initRole(prompt));
RoleContentModel model = new RoleContentModel();
model.setRole(ModelConstant.USER_ROLE);
model.setContent(name);
modelList.add(model);
return modelList;
}
private static List<RoleContentModel> initRole(String prompt) {
List<RoleContentModel> initModelList = new ArrayList<>();
RoleContentModel model = new RoleContentModel();
model.setRole(ModelConstant.SYSTEM);
model.setContent(prompt);
initModelList.add(model);
return initModelList;
}
}
更详细的代码大家可以直接看文章开头的Github仓库。
# 总结 {#总结}
这样一个好用的取名神器就开发完成了,大家有兴趣的话也可以去尝试一下。