yh-codemirror
yh-codemirror
Vue3 的 CodeMirror6 封装,在全屏 / 美化 / 全选 / 复制 / 清空 等基础能力之上,提供 AI 提示编辑(选中或整篇代码 → 自然语言描述 → AI 改写回写)。
基础用法
<yh-codemirror
v-model="code"
:height="500"
language="javascript"
:autocomplete="{javascript: myCompletionSource}">
</yh-codemirror>
language 支持 javascript / sql / css / java / html / json,可传数组;不在列表内会取不到语言插件。
autocomplete 只对 javascript 生效(按语言名取,如 {javascript: myCompletionSource})。
不传 aiRequest 就是一个纯编辑器,不会出现任何 AI 入口。
AI 提示编辑
组件本身不内置任何 AI 服务(保持零业务依赖),由宿主通过 aiRequest 注入请求实现。传入 aiRequest 即自动启用:
- 编辑器右下角出现
AI入口按钮 - 快捷键
Ctrl+I(MacCmd+I,可通过aiHotkey改)唤起输入框 - 有选区 → 只改写选中代码;无选区 → 按
aiScope作用于整个文档或光标处插入 Enter发送、Esc取消;请求中按钮变「停止」,可中断- 支持流式:调用
onChunk逐段返回,编辑器实时写入;非流式则直接返回完整替换文本 - 自动剥离模型常见的
```lang ... ```包裹 - 通过 dispatch 写入,保留 CodeMirror 撤销栈,可
Ctrl+Z回退 - 历史问答:输入框右上角「历史」可查看本轮会话问过的问题与 AI 的完整回答(可展开、可复制、可清空),
每条前面会显示宿主传入的
aiLocation,建议格式「区域名称 · <按钮/查询条件/字段名称 + 类型> · 编辑器字段名」 (如「区域信息 · 事件按钮 · 点击事件」「区域信息 · 名称字段 · 字段值更改」; 直接在区域事件里提问时没有中间层,就是「区域信息 · 数据初始化」)
历史记录存在 ai-history.js 的模块级单例里(上限 50 条,最新在前)。之所以不放组件内:
配置面板里的编辑器会随切换配置项反复卸载重建,组件内的 ref 一卸载就没了。
代价是同一页面所有编辑器共用一份(未做分组),所以每条都带时间、问题原文和 aiLocation——
没有归属信息时,一堆「把名称标红」的提问根本分不清是哪块配置。
Props
| 参数 | 类型 | 默认值 | 说明 |
|---|---|---|---|
aiRequest |
AiCodeEditRequest |
- | AI 请求函数,传入即启用 AI 编辑 |
aiHotkey |
string |
'Mod-i' |
唤起输入框的快捷键(CodeMirror 键位语法) |
aiPlaceholder |
string |
见源码 | 输入框占位提示 |
aiScope |
'document' | 'cursor' |
'document' |
无选区时的作用范围 |
aiPanelSize |
'normal' | 'large' |
'normal' |
AI 输入框 / 历史面板的尺寸档位。编辑区被放大时(如 window-form 的「放大」抽屉)传 large |
aiWindowId |
string | number |
'' |
AI 上下文用的「所属窗口 ID」。组件不认识任何业务接口,只在 AI 被唤起时通过 ai-activate 把它抛给宿主,由宿主去取窗口配置 |
aiLocation |
string |
'' |
这条提问来自哪里(建议「区域名 · 按钮/查询条件/字段名 + 类型 · 编辑器字段名」,如「区域信息 · 事件按钮 · 点击事件」,名称一律取 NAME)。组件只原样记进历史并显示在问题前,不解析其含义 |
Events
| 事件 | 参数 | 说明 |
|---|---|---|
update:modelValue |
string |
代码变更(原有) |
ai-activate |
string | number |
AI 面板刚打开(携带 aiWindowId),宿主可趁此时预取上下文;aiWindowId 为空时不触发 |
ai-apply |
{prompt, text} |
AI 结果已写入编辑器 |
ai-error |
Error |
AI 请求失败 |
Expose
openAi(prompt?)、closeAi()、stopAi()、getView()。
接入示例(非流式)
import {YhCodemirror} from 'yh-codemirror';
import type {AiCodeEditRequest} from 'yh-codemirror';
const aiRequest: AiCodeEditRequest = async ({prompt, code, selectedCode, language, signal}) => {
const res = await fetch('/api/ai/code-edit', {
method: 'POST',
signal,
headers: {'Content-Type': 'application/json'},
body: JSON.stringify({prompt, code, selectedCode, language}),
});
if (!res.ok) {
throw new Error('AI 请求失败');
}
const data = await res.json();
// 返回「作用范围的完整替换文本」,组件负责写入
return data.code;
};
接入示例(流式 SSE)
const aiRequest: AiCodeEditRequest = async ({prompt, selectedCode, signal, onChunk}) => {
const res = await fetch('/api/ai/code-edit/stream', {
method: 'POST',
signal,
headers: {'Content-Type': 'application/json'},
body: JSON.stringify({prompt, code: selectedCode}),
});
const reader = res.body!.getReader();
const decoder = new TextDecoder();
while (true) {
const {done, value} = await reader.read();
if (done) {
break;
}
onChunk?.(decoder.decode(value, {stream: true}));
}
// 走 onChunk 时返回值可为空,组件已实时写入编辑器
};
提示:流式场景下
onChunk传入的是增量片段,组件按累积文本整体覆盖作用区间,因此服务端无需自行做 diff。
Version History
| Version | Released | Vue | License |
|---|---|---|---|
| 1.0.9 | ^3.5.0 | ||
| 1.0.8 | ^3.5.0 | ||
| 1.0.7 | ^3.5.0 | ||
| 1.0.6 | ^3.0.0 | ||
| 1.0.5 | 3.2.47 | ||
| 1.0.4 | 3.2.47 | ||
| 1.0.3 | 3.2.47 | ||
| 1.0.2 | 3.2.47 | ||
| 1.0.0 | 3.2.47 |