51工具盒子

依楼听风雨
笑看云卷云舒,淡观潮起潮落

写代码命名总是很纠结,突发奇想做了个插件

# 前言 {#前言}

我平常是一个比较容易纠结的人,每次遇到那种比较难取的方法名或类名的时候就会很纠结,于是一个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仓库。

# 总结 {#总结}

这样一个好用的取名神器就开发完成了,大家有兴趣的话也可以去尝试一下。

赞(3)
未经允许不得转载:工具盒子 » 写代码命名总是很纠结,突发奇想做了个插件