如何实现一个功能类似Cursor的编辑器,但是富文本编辑器

621 阅读4分钟

最近软件开发领域最火的一个应用之一就是Cursor了。结合了VS Code和ChatGPT的代码编辑工具,这个工具的推出直接让AI从流式对话的窗口应用变身成为生产力工具的最佳搭档。当然也有很多的编辑器都增加了AI的功能,比如我当前一直在用的Notion,我们可以选中一段文字,让AI帮我们润色。当这些软件加入了AI之后,立刻变成了一个更酷的产品。 我们一介平民能否也能做出这种效果的功能呢?答案是肯定的! 下面介绍的这种方法就可以实现在编辑器中调用AI功能,帮我们对文章进行润色。

Lexical

要做到带AI功能的文档编辑器,首先需要一个可灵活自定义的文档编辑器,做了一番调研之后,我选择了Lexical。

Lexical 是由 Meta(前 Facebook)开发的一种现代化的文本编辑框架,用于构建功能强大、高性能且可扩展的富文本编辑器。它基于 React,采用模块化设计,提供了一组核心工具和插件,适用于简单文本输入框到复杂富文本编辑器的开发。

Lexical可以通过自定义插件的方式添加你想要的任何功能。官方提供的demo中已经帮我们实现了常见的富文本编辑器的插件。这个框架会把顶部工具栏、markdown支持、代码块、图片通通以插件的形式存在,需要什么引入什么,甚至可以自己实现插件。

正是因为看重了它的灵活性,我们就可以深度定制这个编辑器。让它集成AI相关的功能,比如实现AI润色功能。

以下是官网demo展示的选中悬浮框。 image.png

这个是我改造之后的悬浮框,增加了一个AI润色的按钮,点击按钮之后会调用后台AI接口进行文字生成,返回给前端进行文字的替换。

image.png

这是润色之后的效果,划线的是原来的内容,黄色的是AI生成的内容。 image.png

因为Lexical灵活性高,这也导致了它的复杂性高,代码量很多。主要包括glugin、node、theme这些。官方提供的插件都是可以修改源码的,大家可以去github上查看和参考。

官网的demo在github上也有源码,我就是参考这个源码来完成这个编辑器功能的开发。这是demo源码地址

悬浮框用到的插件是FloatingTextFormatToolbarPlugin。找准了位置,我们只需要改造这部分代码,引入新的功能按钮,并实现相关的文本操作。

在实现文本操作这块,我们需要了解它的一些API。比如文字范围选择类的API,这里我们用到了insertNodes函数,用于插入ai生成的内容。 这是主要的代码:

const aiGenerate = async () => {
    let selectedText = "";
    editor.update(() => {
        const selection = $getSelection();
        if ($isRangeSelection(selection)) {
            selectedText = selection.getTextContent();
        }
    })
    // 调用ai api 传递选中内容为文本参数,等待后台数据返回
    const data = await insertAi(selectedText)
    editor.update(() => {
        const selection = $getSelection()
        const lineBreakNode = $createLineBreakNode();
        if (selection && $isRangeSelection(selection) && selection.getTextContent() == selectedText) {
        // 将选中的内容、换行节点和ai生成的内容节点插入
            selection.insertNodes([$createTextNode(selectedText).toggleFormat('strikethrough')
                , lineBreakNode
                , $createTextNode(data).setStyle('color: #fbbf24;')
            ])
        }
    })
}

image.png

同时需要在src目录下的index.css文件夹下引入图片信息,不然看不到图标按钮。样式代码格式如下:

i.image {
  background-image: url(images/icons/file-image.svg);
}

后端实现

这是我使用的是Spring AI 和Java 21,当然Java17也是可以的。

大模型的API用的是智普AI,他提供的免费的模型可用,官网申请一个appkey就行。

主要代码如下 :

引入openai和spring ai依赖pom.xml

<dependency>
    <groupId>org.springframework.ai</groupId>
    <artifactId>spring-ai-openai-spring-boot-starter</artifactId>
</dependency>
......
<dependencyManagement>
    <dependencies>
        <dependency>
            <groupId>org.springframework.ai</groupId>
            <artifactId>spring-ai-bom</artifactId>
            <version>1.0.0-SNAPSHOT</version>
            <type>pom</type>
            <scope>import</scope>
        </dependency>
    </dependencies>
</dependencyManagement>

这里引入的是openai的依赖,因为智普ai支持openai api格式,当然你也可以引入智普ai的依赖。Spring AI是支持智普AI 的。

大模型api的信息配置application.yml。

spring:
    ai:
      openai:
        api-key: ${ZHIPU_AI_API_KEY}
        base-url: https://open.bigmodel.cn/api/paas/
        chat:
          api-key: ${ZHIPU_AI_API_KEY}
          base-url: https://open.bigmodel.cn/api/paas/
          completions-path: /v4/chat/completions
          options:
            model: GLM-4-flash

功能接口:

@Component
public class EditorAgent {
    private final ChatClient chatClient;

    public EditorAgent(OpenAiChatModel chatModel) {
        this.chatClient = ChatClient.builder(chatModel)
                .build();
    }

    public Prompt getEditorAgentPromptByUserText(String userText) {
        String systemText = """
                你是一个专业的文字工作者。
                以下是你需要准守的准则:
                文风与语气:选择适合的语气(如正式、轻松、学术性)或特定风格(如叙述性、说明性),以保持作品的风格一致。
                            
                拼写和语法检查:特别关注常见错误或容易混淆的单词,可以创建自定义词库,以适应特定的主题或专业领域。
                            
                简洁表达:提炼要点,将复杂的句子转化为简洁有力的表达,减少不必要的修辞和冗余。
                            
                结构清晰:使用逻辑顺序(如时间顺序、因果关系等)来组织段落,确保文章脉络清晰、流畅。
                            
                精准词汇:根据文章主题选择专业词汇、具象化词语,使表达更具说服力和冲击力。
                """;
        SystemMessage systemMessage = new SystemMessage(systemText);
        Message userMessage = new UserMessage(userText);
        return new Prompt(List.of(userMessage, systemMessage));
    }

    /**
     * AI 润色
     * @param articleSegment 文章片段
     * @return ai生成内容
     */
    public String articlePolish(String articleSegment) {
        String userText = """
            给以下文章片段进行润色。
            片段内容:
            {segment}
            """;
        PromptTemplate promptTemplate = new PromptTemplate(userText);
        Message message = promptTemplate.createMessage(Map.of("segment", articleSegment));
        Prompt editorAgentPrompt= getEditorAgentPromptByUserText(message.getContent());
        OpenAiChatOptions options = OpenAiChatOptions.builder().withTemperature(0.5).build();
        return chatClient.prompt(editorAgentPrompt).options(options).call().content();
    }
}

最后

Lexical这个框架刚开始上手还是比较难得,但当摸索了一段时间之后,就可以驾轻就熟了。Enjoy!

🌈关于我
一位全栈开发工程师。拥有8年开发经验,专长于数据治理平台、大模型应用和后台业务系统开发。目前,我正在开发X.Ryder——一个基于React和Java的WEB开发框架,它还集成了AI功能。这是体验地址:xryder.cn

你可以通过这些方式跟我联系:

感谢你在我的互联网角落停留片刻! 💫