引言
大家好,我是石小石~
有女朋友的同学一定会遇到这样一个问题,经常被女朋友嫌弃听不到他们的话,最后两个人闹得不愉快。 这不,这几天因为和女朋友沟通出了问题,把我拉黑了。哎,女人心,海底针啊!
于是,我突发奇想,为什么不尝试用技术的方式分析情感,看看我的表达是否足够贴近她的期待呢?于是,我决定自己开发一个简单的“女友情感分析工具”,通过AI技术,分析女朋友言语中的情感,判断她开心、平静还是生气!
这样,我岂不要走向人生巅峰?
实现目标
这篇文章,我将完整展示如何实现这个情感分析工具,包括从前端 Vue 3 技术栈、后端 Koa 服务到 AI接口调用,一步步实现整个流程。最终的效果大致如下:
如图,大致效果就是:用户可以输入一段话,点击按钮分析后,工具会返回情感倾向的概率数据,并用柱状图展示分析结果。
技术方案
整个项目的核心其实非常简单,就是调用AI接口分析用户输入的语言,分析后将特定的数据返回给前端,前端做响应的数据处理展示。
市场上AI大模型有很多,国产的如豆包、文心一言、通义千问、kimi 等。考虑到免费、SDK调用简单等因素,本文使用Kimi的月之暗Moonshot模型。关于Kimi的api调用,本文不做过多的赘述,请参考我的其他文章
整个项目分为前端与后端两部分,前端部分用于提交用户输入的数据,与后端进行交互,请求文本分析接口,最终以以柱状图形式展示情感分析结果。后端部分是项目的核心,采用Koa 搭建服务,提供一个接口(调用kimi的模型),最终将数据以特定的json形式返回给前端。
技术实现
后端实现
搭建基础 Koa 服务
我们从最基础的 Koa 框架入手,实现一个最简单的服务。首先通过npm i koa安装依赖并初始化项目,然后,创建 app.js 文件,写入如下代码:
// 引入 Koa 框架依赖
const Koa = require("koa");
// 创建 Koa 实例
const app = new Koa();
// 启动服务器监听3000端口
app.listen(3000, () => {
console.log("服务已启动,监听 http://localhost:3000");
});
创建访问路由
const Koa = require("koa");
const Router = require("@koa/router");
const cors = require("koa2-cors"); // 引入 CORS 中间件
const app = new Koa();
const router = new Router();
// 添加跨域支持
app.use(cors());
// 定义情感分析的 HTTP 接口
router.post("/analyze", async (ctx) => {
const { text } = ctx.request.body;
// 逻辑处理
ctx.body = "这是要返回给前端的数据";
});
// 启用解析 JSON 请求体的中间件
app.use(require("koa-bodyparser")());
app.use(router.routes());
app.use(router.allowedMethods());
// 启动 HTTP 服务器
app.listen(3000, () => {
console.log("服务已启动,监听 http://localhost:3000");
});
上面的代码定义了一个用于情感分析的 POST 接口,提供给前端得调用路径是http://localhost:3000/analyze"。
为了提高代码兼容性,我们同时启用了跨域请求支持和解析 JSON 请求体的中间件:
- 默认 Koa 并不直接支持路由功能,我们引入
@koa/router用来管理不同 URL 地址与其响应逻辑的绑定。 koa2-cors是 Koa 框架中用于支持跨域请求的中间件。
在上面的接口中,我们通过ctx.body 设置服务端响应内容发送给前端,我们现在只需要根据用户输入的值,调用AI接口,设置ctx.body 的值即可。
集成 kimi AI模型配置
const Koa = require("koa");
const Router = require("@koa/router");
const cors = require("koa2-cors"); // 引入 CORS 中间件
const OpenAI = require("openai");
const app = new Koa();
const router = new Router();
// 添加跨域支持
app.use(cors());
// 配置 Moonshot AI 客户端
const client = new OpenAI({
apiKey: "xxx", // 替换成你的 API Key
baseURL: "https://api.moonshot.cn/v1",
});
// 定义情感分析的 HTTP 接口
router.post("/analyze", async (ctx) => {
const { text } = ctx.request.body;
if (!text) {
ctx.status = 400;
ctx.body = { error: "未提供输入文本" };
return;
}
try {
// 调用 Moonshot API
const completion = await client.chat.completions.create({
model: "moonshot-v1-8k",
messages: [
{
role: "system",
content:
"你是一个情感分析模型,用于根据用户输入的文本分析情感,返回一个 JSON 对象,包含三个键值对,分别是 positive_prob, neutral_prob, negative_prob,分别代表正面情感的概率,中性情感的概率,负面情感的概率,概率值的范围是 0-1,概率值越高,情感越倾向于对应的情感类型,请严格按照 JSON 格式返回结果,不要添加任何额外的文本。",
},
{
role: "user",
content: text,
},
],
temperature: 0.3,
});
// 检查 API 返回数据并解析
const response = completion?.choices?.[0]?.message?.content;
if (!response) {
throw new Error("未能从 API 返回有效数据");
}
let sentimentResult = {
positive_prob: 0,
neutral_prob: 0,
negative_prob: 0,
};
try {
// 尝试解析 JSON
sentimentResult = JSON.parse(response);
} catch (error) {
console.error("解析 API 返回数据失败:", error);
ctx.status = 500;
ctx.body = { error: "数据解析失败" };
return;
}
// 正常返回数据给前端
ctx.status = 200;
ctx.body = sentimentResult;
} catch (error) {
console.error("API 调用出错:", error.message);
ctx.status = 500;
ctx.body = { error: "服务器错误" };
}
});
// 启用解析 JSON 请求体的中间件
app.use(require("koa-bodyparser")());
app.use(router.routes());
app.use(router.allowedMethods());
// 启动 HTTP 服务器
app.listen(3000, () => {
console.log("服务已启动,监听 http://localhost:3000");
});
上述代码其实非常简单,核心就是配置 Moonshot AI 客户端,返回特定结构的数据。
const client = new OpenAI({
apiKey: "xxx", // 替换成你的 API Key
baseURL: "https://api.moonshot.cn/v1",
});
上面的代码是kimi的官方配置示例,包括client.chat.completions.create的调用及参数,如果你想具体了解,可以参考我其他文章中的教程:
注意,上述代码中,我们通在message中定义了下面的预设值
{
role: "system",
content:
"你是一个情感分析模型,用于根据用户输入的文本分析情感,返回一个 JSON 对象,包含三个键值对,分别是 positive_prob, neutral_prob, negative_prob,分别代表正面情感的概率,中性情感的概率,负面情感的概率,概率值的范围是 0-1,概率值越高,情感越倾向于对应的情感类型,请严格按照 JSON 格式返回结果,不要添加任何额外的文本。",
}
从而获得了我们想要的数据类型,最终在返回给前端。
启动服务
至此,我们的后端接口服务就完成了,我们在终端中运行服务
node app.js
然后前端就可以访问了。
前段实现
前端得逻辑就非常简单了,我们只需要画一个输入框,提价数据给后端,然后用Eacharts渲染处理对应的结果即可。
我们先实现最基本的css
<template>
<div class="analyzer">
<h1>女友情感分析工具</h1>
<textarea
v-model="textInput"
placeholder="请输入待分析的文本..."
rows="3"
></textarea>
<button @click="analyzeSentiment" :disabled="loading">
{{ loading ? "分析中..." : "开始分析" }}
</button>
<div v-if="error" class="error">{{ error }}</div>
<div v-show="sentimentData" class="result">
<div ref="chart" style="width: 600px; height: 300px"></div>
</div>
</div>
</template>
<script setup>
import { ref, watch, onMounted } from "vue";
import axios from "axios";
// 状态管理
const textInput = ref("");
const loading = ref(false);
const error = ref("");
const sentimentData = ref(null);
// 调用后端接口分析文本情感
const analyzeSentiment = async () => {
};
</script>
<style scoped>
.analyzer {
max-width: 600px;
margin: 50px auto;
text-align: center;
}
textarea {
width: 100%;
padding: 10px;
margin-top: 20px;
margin-bottom: 20px;
}
button {
padding: 10px 20px;
}
.error {
color: red;
margin-top: 10px;
}
.result {
width: 600px;
margin-top: 40px;
}
</style>
然后调用后端接口并使用echarts渲染处理好的数据
<script setup>
import { ref, watch, onMounted } from "vue";
import axios from "axios";
import * as echarts from "echarts";
// 状态管理
const textInput = ref("");
const loading = ref(false);
const error = ref("");
const sentimentData = ref(null);
const chart = ref(null);
// 调用后端接口分析文本情感
const analyzeSentiment = async () => {
if (!textInput.value.trim()) {
error.value = "请输入文本!";
return;
}
loading.value = true;
error.value = "";
try {
const response = await axios.post("http://localhost:3000/analyze", {
text: textInput.value.trim(),
});
sentimentData.value = response.data; // 保存返回数据
console.log("sentimentData: ", sentimentData.value);
} catch (err) {
console.error(err);
error.value = "分析失败,请稍后再试!";
} finally {
loading.value = false;
}
};
// 渲染分析结果图表
const renderChart = () => {
// if (!chart.value || !sentimentData.value) return;
console.log("echarts: ", echarts);
console.log("chart.value: ", chart.value);
console.log(11);
const chartInstance = echarts.init(chart.value);
const option = {
title: {
text: "女友情感分析结果",
left: "center",
},
tooltip: {},
xAxis: {
type: "category",
data: ["开心", "平静", "生气"],
},
yAxis: {
type: "value",
},
series: [
{
name: "概率值",
type: "bar",
data: [
sentimentData.value?.positive_prob || 0,
sentimentData.value?.neutral_prob || 0,
sentimentData.value?.negative_prob || 0,
],
itemStyle: {
color: ({ dataIndex }) => {
return ["#73C9E6", "#FFC107", "#FF5252"][dataIndex];
},
},
},
],
};
chartInstance.setOption(option);
};
// 监听 sentimentData 变化,渲染图表
watch(sentimentData, (newVal) => {
if (newVal) {
renderChart();
}
});
// 确保 DOM 加载完成后初始化 ECharts
onMounted(() => {
renderChart();
});
</script>
大功告成!试试最终效果
不错,看着挺可以!我去对线去!
悲伤的后记
哄了好久,终于和女朋女和好了。晚上,朋友喊我出去喝酒。为了试探下女友的态度,我把女友的回复粘贴到了AI里,准备根据分析结果决定要不要去!
看到结果,我半信半疑,只好继续试探的问女朋友,那我去了?女朋友一直很开心的告诉我可以。
后来,我就真去了,再后来,就没有然后了,女朋友把我电话也删除了。
AI不可信啊,兄弟们!越想越气,垃圾程序直接删除!