2023 是 AI 大爆发的一年,这一年我在我的生产力东西中(一个叫 lowcode 的 vscode 插件)接入了 ChatGPT API,插件也进行了重构,日常搬砖也由于 ChatGPT 的引进发生了很大的改变,写的两篇有关 ChatGPT 的文章也上了热榜第一。

在介绍 ChatGPT 是怎么与 lowcode 插件结合之前,先说说 lowcode 插件的发展历史,毕竟从 2020 年第一个版别发布到现在也迭代 3 年多了。

介绍

轮子的发生

一开始写这么个插件的意图为了拉取 YAPI 接口文档信息生成前端 API 恳求办法,如下

export interface IFetchUserListResult {
  code: number;
  msg: string;
  result: {
    rows: {
      name: string;
      age: number;
      mobile: string;
      address: string;
      tags: string[];
      id: number;
    }[];
    total: number;
  };
}
export interface IFetchUserListParams {
  name?: string;
  page: number;
  size: number;
}
/**
 * 用户列表
 * http://yapi.smart-xwork.cn/project/129987/interface/api/1796953
 * @author 划水摸鱼糊屎工程师
 *
 * @param {IFetchUserListParams} params
 * @returns
 */
export function fetchUserList(params: IFetchUserListParams) {
  return request<IFetchUserListResult>(`${env.API_HOST}/api/user/page`, {
    method: 'GET',
    params,
  });
}

之后增加了依据 JSON 生成 API 恳求、依据 JSON 生成 TS 类型等功用。

物料的概念

再之后便是引进了物料的概念:代码片段和区块。上面说的依据 YAPI 接口信息生成 API 恳求办法、依据 JSON 生成 API 恳求、依据 JSON 生成 TS 类型都属于代码片段,只在当时激活的文件里生成代码。而区块便是在多个文件里生成代码(或许说创立多个文件)。

代码片段

代码片段能够通过右键菜单、输入提示、可视化界面进行运用,区块只能通过可视化界面运用。

右键菜单

我的2023年度关键词:ChatGPT、生产力东西

我的2023年度关键词:ChatGPT、生产力东西

输入提示

我的2023年度关键词:ChatGPT、生产力东西

输入提示类似 vscode 自带的代码片段功用,同时兼容 vscode 代码片段的语法

可视化界面

我的2023年度关键词:ChatGPT、生产力东西

代码片段可视化的功用现在我也很少用到(能够不必,但不能没有)

区块

前面也说了,区块是为了在多个文件里生成代码(或许创立多个文件)。比如写一个 react 组件的时分,或许包括 js 文件和 css 文件。

我的2023年度关键词:ChatGPT、生产力东西

我的2023年度关键词:ChatGPT、生产力东西

不同区块的 Schema 表单不相同,发生不相同的模板数据,就能够到达模板数据 模板生成代码的意图。

内部细节

插件读取项目根目录下的 materials/blocks 作为区块,读取 materials/snippets 作为代码片段。

我的2023年度关键词:ChatGPT、生产力东西

现在已经支撑装备恣意目录,在所有项目中共享物料

代码片段和区块目录内的内容如下

我的2023年度关键词:ChatGPT、生产力东西

我的2023年度关键词:ChatGPT、生产力东西

主要是 src 目录内的内容存在差异,代码片段的 src 目录内必须是 template.ejs 文件,区块的 src 目录内可所以恣意内容。生成代码的时分,运用 ejs 模板引擎编译 ejs 文件。代码片段是将编译今后的内容插入到编辑器光标所在的位置,区块是将编译后的文件拷贝到指定的目录里(非 ejs 文件直接拷贝)。

model.json

默许模板数据

preview.json

物料相关装备

{
	"title": "",
	"description": "",
	"img": [
		"https://www.6hu.cc/wp-content/uploads/2024/01/229735-PMi78A.jpg"
	],
	"category": [],
	"notShowInCommand": true,
	"notShowInSnippetsList": true,
	"notShowInintellisense": true,
	"showInRunSnippetScript": true,
	"schema": "amis",
	"scripts": []
}

schema.json

可视化界面 Schema 表单装备,支撑 form-render、formily、amis。

我的2023年度关键词:ChatGPT、生产力东西

script/index.js

模板编译周期钩子函数

module.exports = {
  beforeCompile: context => {
    console.log(context)
  },
  afterCompile: context => {
    console.log(context)
  },
  complete: context => {
    console.log(context)
  },
}

重构之后会利用这个文件做更多风趣的事

缺点

右键菜单运用代码片段的适用范围有限

export const generateCode = (context: vscode.ExtensionContext) => {
  context.subscriptions.push(
    vscode.commands.registerTextEditorCommand(
      'lowcode.generateCode',
      async () => {
        const rawClipboardText = getClipboardText();
        let clipboardText = rawClipboardText.trim();
        clipboardText = JSON.stringify(jsonParse(clipboardText));
        const validYapiId = isYapiId(clipboardText);
        const validJson = jsonIsValid(clipboardText);
        const valid = validJson || validYapiId;
        if (valid) {
          if (validYapiId) {
            await genCodeByYapi(clipboardText, rawClipboardText);
          } else {
            await genCodeByJson(clipboardText, rawClipboardText);
          }
          return;
        }
        try {
          await genCodeByTypescript(rawClipboardText, rawClipboardText);
        } catch {
          window.showErrorMessage('请仿制Yapi接口ID或JSON字符串或TS类型');
        }
      },
    ),
  );
};

代码里写死了逻辑,只能处理 json 、ts 类型、YAPI 接口。

与 ChatGPT 的结合

引进 ChatGPT 的开始意图是为了翻译区块 Schema 表单对应的模板数据里的指定字段。

翻译物料模板数据指定字段

我的2023年度关键词:ChatGPT、生产力东西

我的2023年度关键词:ChatGPT、生产力东西

点击 Ask ChatGPT 就会翻开 ChatGPT 的 WebView 界面,并自动发送预设的 Prompt。

我的2023年度关键词:ChatGPT、生产力东西

我的2023年度关键词:ChatGPT、生产力东西

预设的 Prompt 便是放在 viewPrompt.ejs 中

我的2023年度关键词:ChatGPT、生产力东西

内容如下:

<%- model %> 将这段 json 中,filters 字段中的 key 字段翻译为英文,运用驼峰语法,label、placeholder
保存中文。columns 字段中的 key、dataIndex 字段翻译为英文,运用驼峰语法,title 字段保存中文。
回来翻译后的 markdown 语法的代码块

这种办法和 ChatGPT 交流会有各种形而上学问题,比如翻译的字段不对或许所有的字段都翻译了,也或许是我写的 Prompt 有问题,这个功用也几乎不必了,后面会介绍另一种办法。

代码片段当作 Prompt 管理东西

看过几个 vscode 里 ChatGPT 的插件,大都是写死几个菜单,比如解说一段代码的意思、重构一段代码、给代码增加单元测试,说实话,有点 low low 的。

我是加了两个菜单:Ask ChatGPT、Ask ChatGPT With Template

我的2023年度关键词:ChatGPT、生产力东西

Ask ChatGPT

逻辑很简单,直接把当时选中的代码或许剪贴板里的内容原封不动的发给 ChatGPT。

vscode.commands.registerCommand('lowcode.askChatGPT', () => {
      showChatGPTView({
        task: {
          task: 'askChatGPT',
          data: getSelectedText() || getClipboardText(),
        },
      });
    }),

其实这个菜单完全能够去掉,用 Ask ChatGPT With Template 也能实现

Ask ChatGPT With Template

顾名思义便是依据不同的场景运用不同的 Prompt 模版去问 ChatGPT。

我的2023年度关键词:ChatGPT、生产力东西

只需要在代码片段的目录下增加 commandPrompt.ejs 文件即可

我的2023年度关键词:ChatGPT、生产力东西

内容或许如下

下面我让你来充任翻译家,你的方针是把中文翻译成英文单词,请翻译时运用驼峰格局,小写字母开头,不要带翻译腔,而是要翻译得天然、流畅和地道,运用美丽和典雅的表达办法。
请翻译下面的内容:“<%- rawSelectedText || rawClipboardText %>”

我的2023年度关键词:ChatGPT、生产力东西

重构、优化

之前提到过右键菜单运用代码片段的适用范围有限,只能处理 json 、ts 类型、YAPI 接口。接入 ChatGPT 后,又加了两个菜单。假如之后要加什么新功用还得接着加菜单,那就太 low 了。

虽然引进了 ChatGPT,可是 ChatGPT 的交互页面是独立的,ChatGPT 回来成果后还需要手动仿制,我这种懒人是无法接受的。

webview 界面调用 nodejs 脚本

可视化界面装备表单还是挺费时的,而且原来的 Ask ChatGPT 的功用也比较形而上学。加了一个“履行脚本”的按钮,能够实现调用物料目录下 src/index.js 文件内指定办法。

我的2023年度关键词:ChatGPT、生产力东西

我的2023年度关键词:ChatGPT、生产力东西

我的2023年度关键词:ChatGPT、生产力东西

在运用 ChatGPT 进行翻译的时分,运用了 TypeChat(关于 TypeChat 能够看 TypeChat、JSONSchemaChat实战 – 让ChatGPT更听你的话),可是并不需要在插件内部引进 TypeChat。如下

我的2023年度关键词:ChatGPT、生产力东西

export async function handleAskChatGPT() {
  const { lowcodeContext } = context;
  const schema = fs.readFileSync(
    path.join(lowcodeContext!.materialPath, 'config/schema.ts'),
    'utf8',
  );
  const typeName = 'PageConfig';
  const res = await translate<PageConfig>({
    schema,
    typeName,
    request: JSON.stringify(lowcodeContext!.model as PageConfig),
    completePrompt:
      `你是一个依据以下 TypeScript 类型界说将用户恳求转换为 "${typeName}" 类型的 JSON 方针的服务,而且依照字段的注释进行处理:n`  
      ````n${schema}```n`  
      `以下是用户恳求:n`  
      `"""n${JSON.stringify(lowcodeContext!.model as PageConfig)}n"""n`  
      `The following is the user request translated into a JSON object with 2 spaces of indentation and no properties with the value undefined:n`,
    createChatCompletion: lowcodeContext!.createChatCompletion,
    showWebview: true,
    extendValidate: (jsonObject) => ({ success: true, data: jsonObject }),
  });
  lowcodeContext!.outputChannel.appendLine(JSON.stringify(res, null, 2));
  if (res.success) {
    return { ...res.data };
  }
  return lowcodeContext!.model;
}

脚本办法履行完后将模版数据(model)回来,省去手动仿制。

能够像写事务代码相同,依据自己需要增加各种处理办法,测验各种新的技能。

假如 schema 表单用的是 amis,还能够在 schema 中装备履行脚本,比如:

{
	"type": "button",
	"label": "插入// lowcode-model-import-api",
	"onEvent": {
		"click": {
			"actions": [{
				"actionType": "runScript",
				"args": {
					"method": "insertPlaceholder",
					"params": "// lowcode-model-import-api"
				}
			}]
		}
	}
}

点击按钮的时分,会调用 insertPlaceholder 办法,参数为 // lowcode-model-import-api

Run Snippet Script

增加了 Run Snippet Script 菜单

我的2023年度关键词:ChatGPT、生产力东西

我的2023年度关键词:ChatGPT、生产力东西

挑选对应的模版(代码片段)后,会履行模版目录下 src/index.js 的 onSelect 办法,办法里能够写任何逻辑。

只需要将代码片段目录下的 config/preview.json 文件里的showInRunSnippetScript 设置为 true,代码片段就会出现在菜单中。

{
	"title": "",
	"description": "",
	"img": [
		"https://www.6hu.cc/wp-content/uploads/2024/01/229735-PMi78A.jpg"
	],
	"category": [],
	"notShowInCommand": false,
	"notShowInSnippetsList": true,
	"notShowInintellisense": true,
	"showInRunSnippetScript": true,
	"schema": "amis",
	"scripts": []
}

这个功用的加入,能够做很多风趣的事情,如下:

axios-request-api

把插件内部依据 YAPI 接口文档信息生成前端 API 恳求办法的代码挪到了外面,而且加了个有意思的功用,让 ChatGPT 生成恳求办法的称号,部分代码如下:

const res = await fetchApiDetailInfo(domain, yapiId, token);
  if (!res.data.data) {
    throw res.data.errmsg;
  }
  funcName = await context.lowcodeContext!.createChatCompletion({
    messages: [
      {
        role: 'system',
        content: `你是一个代码专家,依照用户传给你的 api 接口地址,和接口恳求办法,依据接口地址里的信息推测出一个生动形象的办法称号,驼峰格局,回来办法称号`,
      },
      {
        role: 'user',
        content: `api 地址:${res.data.data.query_path}${res.data.data.method} 办法,作用是${res.data.data.title}`,
      },
    ],
  });
  typeName = `I${funcName.charAt(0).toUpperCase()   funcName.slice(1)}Result`;

完好代码:github.com/lowcode-sca…

OCR

运用百度 OCR 识别图片文字

由于 nodejs 没法读取剪贴板里的图片,只能翻开一个 webview 去读取,中心代码如下:

import { window, Range, env } from 'vscode';
import { generalBasic } from '../../../../../share/BaiduOCR/index';
import { context } from './context';
export async function bootstrap() {
  const { lowcodeContext } = context;
  const clipboardImage = await lowcodeContext?.getClipboardImage();
  const ocrRes = await generalBasic({ image: clipboardImage! });
  const words = ocrRes.words_result.map((s) => s.words).join(',');
  env.clipboard.writeText(words).then(() => {
    window.showInformationMessage('内容已经仿制到剪贴板');
  });
  window.activeTextEditor?.edit((editBuilder) => {
    // editBuilder.replace(activeTextEditor.selection, content);
    if (window.activeTextEditor?.selection.isEmpty) {
      editBuilder.insert(window.activeTextEditor.selection.start, words);
    } else {
      editBuilder.replace(
        new Range(
          window.activeTextEditor!.selection.start,
          window.activeTextEditor!.selection.end,
        ),
        words,
      );
    }
  });
}

启动一个 nestjs 服务

我的2023年度关键词:ChatGPT、生产力东西

import { Controller, Get } from '@nestjs/common';
import { AppService } from './app.service';
@Controller()
export class AppController {
  constructor(private readonly appService: AppService) {}
  @Get()
  getMaterialPath() {
    return this.appService.getMaterialPath();
  }
}
import { Injectable } from '@nestjs/common';
import { context } from './context';
@Injectable()
export class AppService {
  getMaterialPath() {
    return context.lowcodeContext?.materialPath;
  }
}

完好代码:github.com/lowcode-sca…

生成 value-label 格局 JSON

运用了TypeChat,ChatGPT 回来的成果有发问,最终重试之后正确了。

完好代码:github.com/lowcode-sca…

翻译成驼峰格局

代码:

import { env, window, Range } from 'vscode';
import { context } from './context';
export async function bootstrap() {
  const clipboardText = await env.clipboard.readText();
  const { selection, document } = window.activeTextEditor!;
  const selectText = document.getText(selection).trim();
  let content = await context.lowcodeContext!.createChatCompletion({
    messages: [
      {
        role: 'system',
        content: `你是一个翻译家,你的方针是把中文翻译成英文单词,请翻译时运用驼峰格局,小写字母开头,不要带翻译腔,而是要翻译得天然、流畅和地道,运用美丽和典雅的表达办法。请翻译下面用户输入的内容`,
      },
      {
        role: 'user',
        content: selectText || clipboardText,
      },
    ],
  });
  content = content.charAt(0).toLowerCase()   content.slice(1);
  window.activeTextEditor?.edit((editBuilder) => {
    if (window.activeTextEditor?.selection.isEmpty) {
      editBuilder.insert(window.activeTextEditor.selection.start, content);
    } else {
      editBuilder.replace(
        new Range(
          window.activeTextEditor!.selection.start,
          window.activeTextEditor!.selection.end,
        ),
        content,
      );
    }
  });
}

当时目录翻译成英文

代码:

import * as path from 'path';
import * as vscode from 'vscode';
import * as fs from 'fs-extra';
import { context } from './context';
export async function bootstrap() {
  const { lowcodeContext } = context;
  const explorerSelectedPath = path
    .join(lowcodeContext?.explorerSelectedPath || '')
    .replace(/\/g, '/');
  const explorerSelectedPathArr = explorerSelectedPath.split('/');
  const name = explorerSelectedPathArr.pop();
  vscode.window.withProgress(
    {
      location: vscode.ProgressLocation.Notification,
    },
    async (progress) => {
      progress.report({
        message: `loading`,
      });
      let content = await context.lowcodeContext!.createChatCompletion({
        messages: [
          {
            role: 'system',
            content: `你是一个翻译家,你的方针是把中文翻译成英文单词,请翻译时运用驼峰格局,小写字母开头,不要带翻译腔,而是要翻译得天然、流畅和地道,运用美丽和典雅的表达办法。请翻译下面用户输入的内容`,
          },
          {
            role: 'user',
            content: name || '',
          },
        ],
      });
      content = content.charAt(0).toLowerCase()   content.slice(1);
      fs.renameSync(
        path.join(lowcodeContext?.explorerSelectedPath || ''),
        path.join(explorerSelectedPathArr.join('/'), content),
      );
    },
  );
}

快速创立区块

代码:

import * as path from 'path';
import { window } from 'vscode';
import * as fs from 'fs-extra';
import { context } from './context';
import { renderEjsTemplates } from '../../../../../share/utils/ejs';
export async function bootstrap() {
  const { lowcodeContext } = context;
  const result = await window.showQuickPick(
    [
      'uniapp/vue3-mvp',
      'uniapp/vue3-mvp emit',
      'uniapp/vue3-mvp props',
      'uniapp/vue3-mvp props emit',
    ].map((s) => s),
    { placeHolder: '请挑选模板' },
  );
  if (!result) {
    return;
  }
  const tempWorkPath = path.join(
    lowcodeContext?.env.rootPath || '',
    '.lowcode',
  );
  fs.copySync(path.join(lowcodeContext?.materialPath || ''), tempWorkPath);
  await renderEjsTemplates(
    {
      createBlockPath: path
        .join(lowcodeContext?.explorerSelectedPath || '')
        .replace(/\/g, '/'),
    },
    path.join(tempWorkPath, 'src'),
  );
  fs.copySync(
    path.join(tempWorkPath, 'src', result),
    path.join(lowcodeContext?.explorerSelectedPath || ''),
  );
  fs.removeSync(tempWorkPath);
}

翻开 WebView

我的2023年度关键词:ChatGPT、生产力东西

右边 WebView 是一个独立的工程,部署在 vercel 上,主要为了学一下 UnoCSS,后续或许会把 screenshot-to-code 抄过来

完好代码:github.com/lowcode-sca…

WebView 项目代码(Vue):lowcode-webview-vue

WebView 项目代码(React):lowcode-webview-react-vite

无限或许

上面列举了我常用的一些功用,以及正在测验的东西,能够看出 lowcode 插件的自由度已经很高了,后续假如出现了什么好玩的技能能够当即接入玩一下。

遗憾

2023 对图片相关的 AI 研讨的比较少,也想不到有什么运用场景。

2024 研讨一下 Design to Code AI 的落地。

源码

插件源码:github.com/lowcoding/l…

物料源码:github.com/lowcode-sca…