我的Todolist有AI

1,012 阅读4分钟

在最近的学习中,初步了解了vue的一些使用方法以及之前学过一些简单openai,于是我产生了这个想法,将两者结合来增强我的实战能力并让我增加熟练的使用vue。接下来让我来简单介绍一下我的todo-list吧。

页面展示

image.png

目录结构

image.png

布局

image.png

本次实战我使用的是BEN命名方式,由于不太熟练取名可能不准确请各位见谅。

todo-list__title: 用于存放标题。

todo-list__container: 用于存放todo-list的主体内容。

  • todo-list__container_body:存放了input框和添加按钮。
  • todo-list__container_content:其中有todo-list__container_ul和todo-list__container_footer,分成存放list列表和一些按钮。

todo-list__res: 用于存放openai返回的结果。

rodo-list主要功能实现

本项目我使用的是reactive来实现list的响应式变化,todos是我用于存放数据的一个容器。

const state = reactive({
    todos: []
});

增加列表

为实现这个功能,我主要用到了@clickv-model,给add按钮添加绑定事件,用v-model实现双向绑定来获取输入框内的值。(这里仅涉及功能实现,后面介绍ai时会重新编写这里)

let todoAdd = ref('');
const Add = (text) => {
    text = todoAdd.value.trim();
    if (text === '') {
        alert('输入不能为空');
    } else {
        state.todos.push({ id: state.todos.length + 1, text: todoAdd.value, done: false, action: '' });
        todoAdd.value = '';
    }
}

删除列表

从增加列表可以看出我给list添加了一个id属性,用于删除。当点击删除时,点击事件会将要删除的list的id传给删除方法,我使用的是filter筛选,将不是这个id的list重新传给todos。

<body>
<button type="button" @click="todoDel(item.id)" class="del">del</button>

<script>
const todoDel = (id) => {
    state.todos = state.todos.filter(todo => todo.id !== id);
    state.todos.forEach((todo, index) => {
        todo.id = index + 1;
    });
}
</script>
</body>

将完成list用删除线划掉

这个功能我使用了:class动态监测list的done属性,并用其属性结果命名,当点击时会改变list的done属性。当为true时会触发text-decoration: line-through;样式

<style>
.done {
    text-decoration: line-through;
}
</style>
<body>
<span @click="todoDone(item.id)" :class="{ 'done': item.done }">{{ item.text }}</span>

<script>
const todoDone = (id) => {

    state.todos.forEach((item) => {
        if (item.id === id) {
            item.done = !item.done;
        }
    });
}
</script>
</body>

查看全部列表、已完成列表、未完成列表

这里我使用的是computed 属性,它是用来计算并缓存基于其他数据(响应式依赖)的值。点击不同按钮传入不同状态,根据不同状态返回筛选出来的list。

<body>
<div class="todo-list_footer_btn">
      <div class="All"><span @click="setFilter('all')">&nbsp;&nbsp;&nbsp;</span></div>
      <div class="Active"><span @click="setFilter('active')">未完成</span></div>
      <div class="Completed"><span @click="setFilter('completed')">&nbsp;&nbsp;&nbsp;</span></div>
</div>
<script>
const currentFilter = ref('all');
const setFilter = (type) => {
    currentFilter.value = type;
}

const filteredTodos = computed(() => {
    switch (currentFilter.value) {
        case 'active':
            return state.todos.filter(todo => !todo.done);
        case 'completed':
            return state.todos.filter(todo => todo.done);
        default:
            return state.todos; // 显示全部
    }
})

</script>
</body>

项目升级(引入AI)

在本项目中AI主要实现的是根据用户添加的list给出相应的规划安排。

效果如下

功能实现

一、导包并引入项目

使用包管理器将本次ai需要使用的资源导入,在项目文件终端使用如npm i @koa/bodyparser等指令。

import bodyParser from '@koa/bodyparser';
import Koa from 'koa'//后端简单框架
import Router from 'koa-router';//路由对象来监听端口资源请求
// 多模态能力
import OpenAI from "openai";
import cors from '@koa/cors';//跨域
import aiChat from './chat.mjs';
import dotenv from 'dotenv';
二、前端

这里的实现场景是:用户添加list,点击add获取输入框的内容,添加至list中并将内容传给后端,后端获取到内容调用ai接口产生结果后返回给前端响应。 这里主要实现了点击添加后向端口发出post请求,并将输入框内容以json数组的格式在请求体中一起发出。后端响应后,接收到数据,存储在list的action属性中,如何渲染到页面中。

const Add = (text) => {
    text = todoAdd.value.trim();
    if (text === '') {
        alert('输入不能为空');
    } else {

        state.todos.push({ id: state.todos.length + 1, text: todoAdd.value, done: false, action: '' });
        let data = {
            value: text
        }
        fetch('http://localhost:3000/api', {
            method: 'POST',
            headers: {
                'Content-Type': 'application/json'
            },
            body: JSON.stringify(data)
        }).then(res => res.json())//响应 json
            .then(data => {
                if (data.status == 500) {
                    console.log("500:", data);
                }
                if (data.status == 200) {
                    state.action = data.res;
                    console.log(data.res);
                    state.todos[state.todos.length - 1].action = data.res;
                }
            }
            )

        todoAdd.value = '';
    }
}
三、后端

创建一个openai实例,通过这个让api给我们做一个apenai的请求转发,然后获取到前端传过来的内容交给chat。在chat.mjs中我使用了'gpt-3.5-turbo'模型,以对话的形式(即prompt)让ai返回我需要的内容。拿到openAI返回值后返回给前端。

main.mjs

import bodyParser from '@koa/bodyparser';
import Koa from 'koa'//后端简单框架
import Router from 'koa-router';//路由对象来监听端口资源请求
// 多模态能力
import OpenAI from "openai";
import cors from '@koa/cors';//跨域
import aiChat from './chat.mjs';
import dotenv from 'dotenv';
dotenv.config();
const client = new OpenAI({
    apiKey: process.env.OPENAI_KEY,
    //gptsapi会帮我们做apenai的请求转发
    baseURL: 'https://api.302.ai/v1'
});

// web 服务就是一个app
const app = new Koa();
app.use(bodyParser());
// 路由实例
const router = new Router();
app.use(cors());
router.post('/api', async ctx => {
    let { value } = ctx.request.body;
    try {
        const response = await aiChat(client, value);
        ctx.response.body = {
            status: 200,
            res: response
        };
    } catch (err) {
        ctx.response.body = {
            status: 500,
            error: err.message
        }
    }
})
router.get('/', ctx => {
    ctx.body = '首页'
})
app.use(router.routes());
const PORT = 3000;
// http 协议的提供
app.listen(PORT, () => {
    console.log(`server is running at http://localhost:${PORT}`);
});

aiChat.mjs

export default async (client, prompt) => {
    const response = await client.chat.completions.create({
        model: 'gpt-3.5-turbo', // 适合聊天的模型 很多种
        messages: [
            {
                role: 'user',
                content: `
                你是一位擅长给人提供建议的专家,请根据用户接下来的${prompt}活动,给出3条的活动规划方案,每条规划要求简短, 总字数不超过100字,并且都以中文返回。
                `
            }
        ],
        n: 1
    })
    return response.choices[0].message.content;
};

总结

这次的实战让我对于vue3中的很多指令有了更深层次的理解,非常深刻的意识到实战能力对于程序员的重要性。