使用Ollama+qwen2.5为前端开发添砖加瓦
haoteby 2024-12-09 12:48 1 浏览
一、需求说明
我们在日常做后台类的前端开发通常会遇到很多重复性的工作,比如:
1.1 数据交互实体类的声明
一般来说,我们需要根据需求进行数据建模,比如编写一个企业管理的功能,我们需要对企业进行建模。
- 明确企业包含了哪些业务字段
- 明确字段的数据类型
- 一些字段的表单验证
1.2 API请求接口的声明
我们需要声明API调用的基础路径,比如用户模块应该是 /user,企业是 /company,同时也需要明确我们使用的数据模型
1.3 列表页和编辑页的编写
我们会使用封装的 Table Form 等自定义组件来完成列表页和编辑页面的开发。
二、需求分析
上述的一些日常操作中,目前大部分都是能被 AI 取代的。
比如我们可以为模型定义一些模板代码,然后通过与模型对话来调整输出的代码,最后直接通过模型分析 function calling 来实现函数调用直接保存文件。
思路已经有了,那我们来编写一个自定义的脚本服务来完成这些操作吧。
这里的前提是,你本地已经安装了 Ollama 和 qwen2.5 模型(你可以随意选择,我本地是 qwen2.5-7b 和 14b)
三、设计思路
我们的交互流程图如下:
好,有了这个流程,我们可以着手编码了。
四、编码实现
const readline = require('readline');
const path = require("path")
const fs = require("fs")
const prompt = '' // 篇幅太长,一会我会单独把我的 Prompt 放出来。
const history = []
const SYSTEM = 'system'
const ASSISTANT = 'assistant'
const USER = 'user'
const OLLAMA_URL = 'http://localhost:11434/api/chat'
const OLLAMA_REQUEST_OPTIONS = {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
}
const OLLAMA_REQUEST_BODY = {
model: 'qwen2.5:latest',
stream: true,
}
const command = readline.createInterface({
input: process.stdin,
output: process.stdout
});
command.on('close', () => {
process.exit(0);
});
const dir = process.cwd()
function requestCommand() {
command.question('请说: ', async (answer) => {
if (answer.toLowerCase() === '退出') {
command.close();
return
} else {
await request(answer)
requestCommand();
}
});
}
const tools = [{
type: 'function',
function: {
name: "saveCodeFile",
description: "从给定的JSON字符串中提取文件名和代码保存到文件",
parameters: {
type: "object",
properties: {
fileName: {
type: "string",
description: "保存的文件名,如 XXX.ts",
},
codes: {
type: "string",
description: "需要保存的代码文件内容",
},
},
required: ["fileName", "codes"],
},
}
}];
async function init() {
history.push({
role: SYSTEM,
content: prompt,
tools
})
await request('开始吧')
}
init()
async function saveFile() {
const message = history[history.length - 1]
const post = {
...OLLAMA_REQUEST_BODY,
messages: [{
role: USER,
content: message.content
}],
stream: false,
tools
}
const res = await fetch(OLLAMA_URL, {
...OLLAMA_REQUEST_OPTIONS,
body: JSON.stringify(post),
});
const data = await res.json()
if (data.message && data.message.tool_calls && data.message.tool_calls.length > 0) {
try {
const func = data.message.tool_calls[0].function.arguments
// 写到文件
const filePath = path.join(dir, func.fileName)
fs.writeFileSync(filePath, func.codes)
console.log('文件保存成功')
return
} catch (e) {
console.log(e)
}
} else {
console.log('文件保存失败')
}
}
async function request(message) {
if (message === "保存") {
await saveFile()
requestCommand()
return
}
const res = await fetch(OLLAMA_URL, {
...OLLAMA_REQUEST_OPTIONS,
body: JSON.stringify({
...OLLAMA_REQUEST_BODY,
messages: [
...history,
{
role: USER,
content: message
}
],
})
});
if (!res.ok) {
throw new Error(`Network Error: ${res.statusText}`);
}
const reader = res.body.getReader();
const decoder = new TextDecoder('utf-8');
try {
let response = ''
while (true) {
const { done, value } = await reader.read();
if (done) break;
const chunk = decoder.decode(value, { stream: true });
try {
const obj = JSON.parse(chunk)
response += obj.message.content
process.stdout.write(obj.message.content);
} catch (e) {
console.log(e)
}
}
process.stdout.write("\n");
history.push({
role: ASSISTANT,
content: response
})
requestCommand()
} catch (err) {
console.error('Error reading stream:', err);
}
}
好的,代码编写完毕,那么接下来,我们把这个文件起名为 Assistant.js 并放到我的 Home 目录下,现在我可以在任意的文件夹内打开终端输入:
node ~/Assistant.js
即可开始:
- 生成实体类
- 生成列表页
更多的就不演示啦,欢迎大家自行体验~
五、我使用的Prompt
为了篇幅,这里单独附上:
const prompt = `
# 你是一个前端开发工程师,你擅长 **TypeScript** 和 **Vue3**
## 你的职责
理解我的意思,然后按我的要求为我生成 **模型** **Service** **列表页** **编辑页** **选择页** **索引文件** 这几个文件。
你只需要第一次询问我模型名称,第二次询问我哪些属性,然后生成文件,生成完毕后,请询问我是否保存。
## 代码模板示例
你通过理解 我提供的信息,不要询问我英文、数据类型、是否必填等,请你自行判断,并为我生成如下文件:
- **模型文件**
模型文件的文件名为 **UserEntity**,文件内容为:
\`\`\`ts
/**
* # 用户模型
* @author Hamm.cn
*/
export class UserEntity extends BaseEntity {
@Form({
requiredString: true,
})
@Table()
@Search()
@Field({
label: '真实姓名',
})
realname!: string
@Form({
requiredNumber: true,
min: 18,
max: 65
})
@Table()
@Search()
@Field({
label: '年龄',
})
age!: number
}
\`\`\`
- **Service 文件**
> 假设你已经知晓我需要你新建一个用户的API服务 **Service**
Service 文件的文件名为 **UserService**,文件内容为:
\`\`\`ts
/**
* # 用户 Service
* @author Hamm.cn
*/
export class UserService extends AbstractBaseService<UserEntity> {
baseUrl = 'user'
entityClass = UserEntity
}
\`\`\`
- **列表页文件**
列表页的文件名为 **list.vue**,文件内容为:
\`\`\`ts
<template>
<APanel>
<AToolBar
:loading="isLoading"
:entity="UserEntity"
:service="UserService"
@on-add="onAdd"
@on-search="onSearch"
/>
<ATable
v-loading="isLoading"
:data-list="response.list"
:entity="UserEntity"
:ctrl-width="150"
show-enable-and-disable
@on-edit="onEdit"
@on-delete="onDelete"
@on-enable="onEnable"
@on-disable="onDisable"
/>
<template #footerLeft>
<APage
:response="response"
@on-change="onPageChanged"
/>
</template>
</APanel>
</template>
<script lang="ts" setup>
import {
APage, APanel, ATable, AToolBar,
} from '@/airpower/component'
import { useAirTable } from '@/airpower/hook/useAirTable'
import { UserEditor } from './component'
const {
isLoading, response,
onPageChanged, onDelete, onEdit, onAdd, onSearch, onEnable, onDisable,
} = useAirTable(UserEntity, UserService, {
editView: UserEditor,
})
</script>
<style scoped lang="scss"></style>
\`\`\`
- **详情页文件**
详情页的文件名为 **editor.vue**,文件内容为:
\`\`\`html
<template>
<ADialog
:title="title"
:form-ref="formRef"
:loading="isLoading"
confirm-text="保存"
:fullable="false"
@on-confirm="onSubmit"
@on-cancel="onCancel"
>
<el-form
ref="formRef"
:model="formData"
label-width="120px"
:rules="rules"
@submit.prevent
>
<AFormField field="realname" />
<AFormField field="age" />
</el-form>
</ADialog>
</template>
<script lang="ts" setup>
import { ADialog, AFormField } from '@/airpower/component'
import { airPropsParam } from '@/airpower/config/AirProps'
import { useAirEditor } from '@/airpower/hook/useAirEditor'
const props = defineProps(airPropsParam(new UserEntity()))
const {
isLoading, formData, formRef, title, rules,
onSubmit,
} = useAirEditor(props, UserEntity, UserService)
</script>
<style scoped lang="scss">
</style>
\`\`\`
- **选择页面**
选择页面的文件名为 **selector.vue**,文件内容为:
\`\`\`html
<template>
<ASelector
:entity="UserEntity"
:service="UserService"
:props="props"
:editor="UserEditor"
/>
</template>
<script lang="ts" setup>
import { ASelector } from '@/airpower/component'
import { airPropsSelector } from '@/airpower/config/AirProps'
import { UserEditor } from '.'
const props = defineProps(airPropsSelector<UserEntity>(new UserEntity()))
</script>
<style scoped lang="scss"></style>
\`\`\`
- **索引文件**
索引文件的文件名为 **index.ts**,文件内容为:
\`\`\`ts
import UserSelector from './selector.vue'
import UserEditor from './editor.vue'
export {
UserSelector,
UserEditor,
}
\`\`\`
## 你的要求
请注意,我需要你引导我一步步提供信息。我提供完信息之后,你将记住我的信息,然后询问我下一步你需要的信息。
不要一次性生成所有文件,生成完一个文件之后询问我是否保存,然后再提供下一个文件。
询问我时请 **简短描述**(20字以内) 你需要的信息,不要举例,我会准确提供。
不要告诉我全流程,请一次会话引导我做一件事情。
## 回复格式
- 询问模型时:“请告诉我需要生成的模型名称?”
- 询问属性时:“请告诉我 {模型名称} 有哪些属性?”
- 输出生成的文件请使用JSON,如下:
\`\`\`json
{
"filaName": "xxx.ts",
"codes": "{CODES}"
}
\`\`\`
然后接着生成下一个文件。
`
这里我们提供了一些模板代码,于是 AI 就会按照我们的模板代码来生成啦。
六、总结和思考
我们今天用 Ollama + qwen2.5 来实现了代码的快速生成和保存,接下来我们可以基于生成的代码简单的做一些调整,并放到更合适的目录内,优化一下 import 的路径即可。
加上我们使用了很标准的前端基础框架,配合我们的开源项目 AirPower4T ,我们可以快速开发出我们想要的功能。
这大幅度提高了我们的工作效率,于是我们有更多的时间用来学习(摸鱼)了。
That's all~
Bye.
原文链接:https://juejin.cn/post/7439193362323783680
相关推荐
- 网站seo该怎么优化
-
一、网站定位在建设一个网站之前,我们首先要做的就是一个网站清晰的定位,会带来转化率相对较高的客户群体,我们建站的目的就是为了营销,只有集中来做某一件事,才会更好的展现我们的网站。在做SEO优化的同时...
- 3个小技巧教你如何做好SEO优化
-
想半路出家做SEO?可是,怎么才做的好呢?关于SEO专业技术弄懂搜索引擎原理,咱们做搜索引擎排名的首先就是要了解搜索引擎的工作原理,对SEO优化有更深入了解之后再来做SEO,你就能从搜索引擎的视点...
- SEO指令分享:filetype指令
-
filetype用于搜索特定的文件格式。百度和谷歌都支持filetype指令。比如搜索filetype:pdf今日头条返回的就是包含今日头条这个关键词的所有pdf文件,如下图:百度只支持:pdf...
- 网站seo优化技巧大全
-
SEO在搜索引擎中对检索结果进行排序,看谁最初是在用户的第一眼中看到的。实际上,这些排名都是通过引擎的内部算法来实现的。例如,百度算法很有名。那么,对百度SEO的优化有哪些小技巧?下面小编就会说下针对...
- 小技巧#10 某些高级的搜索技巧
-
由于某些原因,我的实验场所仅限百度。1.关键词+空格严格说来这个不能算高级,但关键词之间打空格的办法确实好用。我习惯用右手大拇指外侧敲击空格键,这个习惯在打英文报告时尤其频繁。2.site:(请不要忽...
- MYSQL数据库权限与安全
-
权限与安全数据库的权限和数据库的安全是息息相关的,不当的权限设置可能会导致各种各样的安全隐患,操作系统的某些设置也会对MySQL的安全造成影响。1、权限系统的工作原理...
- WPF样式
-
UniformGrid容器<UniformGridColumns="3"Rows="3"><Button/>...
- MySQL学到什么程度?才有可以在简历上写精通
-
前言如今互联网行业用的最多就是MySQL,然而对于高级Web面试者,尤其对于寻找30k下工作的求职者,很多MySQL相关知识点基本都会涉及,如果面试中,你的相关知识答的模糊和不切要点,基...
- jquery的事件名称和命名空间的方法
-
我们先看一些代码:当然,我们也可以用bind进行事件绑定。我们看到上面的代码,我们可以在事件后面,以点号,加我们的名字,就是事件命名空间。所谓事件命名空间,就是事件类型后面以点语法附加一个别名,以便引...
- c#,委托与事件,发布订阅模型,观察者模式
-
什么是事件?事件(Event)基本上说是一个用户操作,如按键、点击、鼠标移动等等,或者是一些提示信息,如系统生成的通知。应用程序需要在事件发生时响应事件。通过委托使用事件事件在类中声明且生成,且通过...
- 前端分享-原生Popover已经支持
-
传统网页弹窗开发需要自己处理z-index层级冲突、编写点击外部关闭的逻辑、管理多个弹窗的堆叠顺序。核心优势对比:...
- Axure 8.0 综合帖——新增细节内容
-
一、钢笔工具与PS或者AI中的钢笔工具一样的用法。同样有手柄和锚点,如果终点和起点没有接合在一起,只要双击鼠标左键即可完成绘画。画出来的是矢量图,可以理解为新的元件。不建议通过这个工具来画ICON图等...
- PostgreSQL技术内幕28:触发器实现原理
-
0.简介在PostgreSQL(简称PG)数据库中,触发器(Trigger)能够在特定的数据库数据变化事件(如插入、更新、删除等)或数据库事件(DDL)发生时自动执行预定义的操作。触发器的实现原理涉及...
- UWP开发入门(十七)--判断设备类型及响应VirtualKey
-
蜀黍我做的工作跟IM软件有关,UWP同时会跑在电脑和手机上。电脑和手机的使用习惯不尽一致,通常我倾向于根据窗口尺寸来进行布局的变化,但是特定的操作习惯是依赖于设备类型,而不是屏幕尺寸的,比如聊天窗口的...