WCP 5.0.3 把 AI 放进 TipTap 编辑器:选中文字即可润色、续写与替换

2026-09-01 36 预计阅读时间: 1 分钟
来源: oschina.net AI 摘要 Original link

Disclaimer: This article is an AI-assisted summary. Read it together with the original source when precision matters. The summary may omit context, version differences, or edge cases and is not official documentation.

预计阅读时间:10 分钟

WCP 5.0.3 将 AI 操作直接嵌入富文本编辑器:用户选中一段文字后,界面浮出 AI 按钮,可以继续执行润色、续写、翻译、总结或简化,并把生成结果替换到原位置,或者插入当前文档。关键变化不只是增加了几个 AI 命令,而是把调用 AI 的动作压缩进现有写作流程,省掉切换窗口、复制文本、粘贴结果等步骤。

AI 功能为什么适合出现在选区旁边

传统 AI 写作工具往往要求用户离开编辑器:复制原文,打开对话框,解释任务,再把输出贴回来。每次跳转都带来额外成本,还容易丢失段落位置、标题层级和当前写作意图。

选区式交互把上下文变得明确:

  • 用户选中的文本就是本次处理对象,不需要重复说明范围。
  • AI 按钮靠近选区出现,操作与当前注意力位置一致。
  • 结果可以原地替换,也可以作为新内容插入,适配润色和续写两类不同任务。
  • 编辑器仍然是主界面,AI 只是其中一个文本工具,而不是另一个独立工作台。

这里需要区分两种写入语义。润色、翻译、总结和简化通常适合“替换选区”;续写更适合“在选区后插入”。如果所有命令都直接覆盖原文,用户很容易因为误操作丢失内容。

在 TipTap 中可以怎样实现

下面是一个可改造的最小 React 示例。它不是对 WCP 内部实现的推断,而是根据“选中文字后浮出 AI 操作”这一交互方式给出的实践方案。

先创建项目并安装依赖:

npm create vite@latest tiptap-ai-demo -- --template react
cd tiptap-ai-demo
npm install
npm install @tiptap/react @tiptap/starter-kit @tiptap/extension-bubble-menu
npm run dev

src/App.jsx 替换为以下内容。示例使用本地函数模拟 AI 响应,因此可以直接运行;接入真实模型时,只需替换 runAI 函数。

import { useState } from 'react'
import { EditorContent, useEditor } from '@tiptap/react'
import BubbleMenu from '@tiptap/extension-bubble-menu'
import StarterKit from '@tiptap/starter-kit'
import './App.css'

const actions = {
  polish: '润色',
  continue: '续写',
  translate: '翻译',
  summarize: '总结',
  simplify: '简化',
}

async function runAI(action, text) {
  await new Promise(resolve => setTimeout(resolve, 400))

  const demoResults = {
    polish: `经过润色:${text}`,
    continue: `${text} 接下来可以围绕这一观点补充事实、案例和边界条件。`,
    translate: `Translation: ${text}`,
    summarize: `摘要:${text.slice(0, 30)}${text.length > 30 ? '……' : ''}`,
    simplify: `简化表达:${text}`,
  }

  return demoResults[action]
}

export default function App() {
  const [loading, setLoading] = useState(false)

  const editor = useEditor({
    extensions: [StarterKit, BubbleMenu],
    content: `
      <h2>选中这段文字试一试</h2>
      <p>把人工智能能力放进编辑器,可以减少复制、切换窗口和重新定位内容带来的操作成本。</p>
    `,
  })

  async function handleAction(action) {
    if (!editor || loading) return

    const { from, to } = editor.state.selection
    const selectedText = editor.state.doc.textBetween(from, to, ' ')
    if (selectedText.trim().length < 2) return

    setLoading(true)
    try {
      const result = await runAI(action, selectedText)
      const chain = editor.chain().focus()

      if (action === 'continue') {
        chain.insertContentAt(to, `<p>${result}</p>`).run()
      } else {
        chain.insertContentAt({ from, to }, result).run()
      }
    } finally {
      setLoading(false)
    }
  }

  if (!editor) return null

  return (
    <main>
      <BubbleMenu
        editor={editor}
        shouldShow={({ state }) => !state.selection.empty}
      >
        <div className='ai-menu'>
          {Object.entries(actions).map(([action, label]) => (
            <button
              key={action}
              disabled={loading}
              onClick={() => handleAction(action)}
            >
              {loading ? '处理中…' : `AI ${label}`}
            </button>
          ))}
        </div>
      </BubbleMenu>
      <EditorContent editor={editor} />
    </main>
  )
}

再把下面的样式写入 src/App.css

body {
  margin: 0;
  color: #202124;
  background: #f5f6f8;
  font-family: system-ui, sans-serif;
}

main {
  max-width: 760px;
  min-height: 420px;
  margin: 48px auto;
  padding: 32px;
  background: white;
  border: 1px solid #dfe1e5;
}

.ProseMirror {
  min-height: 320px;
  line-height: 1.8;
  outline: none;
}

.ai-menu {
  display: flex;
  gap: 6px;
  padding: 6px;
  background: #202124;
  border-radius: 6px;
  box-shadow: 0 4px 14px rgb(0 0 0 / 20%);
}

.ai-menu button {
  padding: 6px 9px;
  color: white;
  background: transparent;
  border: 0;
  cursor: pointer;
}

.ai-menu button:hover {
  background: #3c4043;
}

.ai-menu button:disabled {
  cursor: wait;
  opacity: 0.6;
}

这段实现展示了选区式 AI 编辑的核心链路:读取 TipTap selection,提取纯文本,调用处理函数,然后按命令类型替换选区或在选区后插入内容。生产环境还应保存选区位置或创建事务映射,避免 AI 请求期间用户继续编辑,导致返回结果写入错误位置。

接入真实模型时,别把密钥放进浏览器

前端应该调用自己的服务端接口,由服务端保存模型密钥、限制输入长度并记录必要的审计信息。下面是一个可以改造的请求约定:

curl -X POST http://localhost:3000/api/ai/edit \
  -H 'Content-Type: application/json' \
  -d '{
    "action": "polish",
    "text": "这是一段需要调整得更加清楚的文字。",
    "locale": "zh-CN"
  }'

服务端可以把动作映射到固定提示词,而不是允许浏览器提交任意系统提示词:

const instructions = {
  polish: '润色文本,保持事实和原意不变,只返回修改后的正文。',
  continue: '续写文本,保持语气一致,只返回新增内容。',
  translate: '将文本翻译为目标语言,只返回译文。',
  summarize: '压缩为简洁摘要,不补充原文之外的事实。',
  simplify: '简化句式和词汇,保留关键信息。',
}

app.post('/api/ai/edit', async (req, res) => {
  const { action, text, locale = 'zh-CN' } = req.body

  if (!instructions[action]) {
    return res.status(400).json({ error: 'unsupported_action' })
  }
  if (typeof text !== 'string' || text.length < 2 || text.length > 5000) {
    return res.status(400).json({ error: 'invalid_text' })
  }

  const result = await callYourModel({
    instruction: instructions[action],
    text,
    locale,
  })

  res.json({ result })
})

callYourModel 需要替换为实际使用的模型 SDK。固定动作、限制文本长度和只返回正文,可以降低提示词注入、费用失控以及模型输出说明性废话的概率,但不能完全消除这些风险。

上线前要补齐的产品边界

AI 原地改写很顺手,也意味着错误结果可能更快进入正式文档。实际部署时建议检查以下事项:

  • 保留撤销能力:每次 AI 写入都应成为独立的 TipTap transaction,让用户可以用一次撤销恢复原文。
  • 提供结果预览:高风险文档可先显示原文与结果差异,再由用户确认替换。
  • 处理并发编辑:AI 返回前文档可能已变化,需要校验 selection、文档版本或协同编辑位置映射。
  • 限制敏感数据外发:明确哪些知识库、客户资料和内部文档允许发送给模型服务。
  • 保留结构信息:只提取纯文本可能丢失链接、加粗和列表格式;复杂场景应同时处理 TipTap JSON,并严格校验模型返回结构。
  • 区分失败状态:超时、限流和内容审核失败不能覆盖原文,应给出可重试反馈。

WCP 5.0.3 所体现的方向,是让 AI 从独立对话窗口变成编辑器中的上下文工具。采用这类能力时,值得优先做好三个基础环节:准确读取选区、可撤销地写回结果,以及通过服务端控制模型访问。只有这些边界可靠,选中文字后“一点即用”才不会以文档安全和编辑确定性为代价。


相关推荐