AI 应用之冰球:对大模型应用的小尝试
在 AI 应用快速发展的当下,"AI 应用之冰球" 通过将宠物照片转化为冰球运动员形象,为用户提供了趣味性与社交属性兼具的体验。该应用基于 Vue3 开发,核心在于通过表单收集用户需求、上传图片并调用 Coze 工作流 API 实现 AI 生成。本文将结合代码与业务笔记,详细解析各模块的实现逻辑、意义及注意事项。
以下是源代码:
<template>
<div class="container">
<div class="input">
<div class="file-input">
<input
type="file"
ref="uploadImage"
accept="image/*"
@change="updateImageData"
required
/>
</div>
<img :src="imgPreview" alt="" v-if="imgPreview"/>
<div class="settings">
<div class="selection">
<label >队服编号:</label>
<input type="number" v-model="uniform_number"/>
</div>
<div class="selection">
<label >队服颜色:</label>
<select v-model="uniform_color">
<option value="红">红</option>
<option value="蓝">蓝</option>
<option value="绿">绿</option>
<option value="白">白</option>
<option value="黑">黑</option>
</select>
</div>
</div>
<div class="settings">
<div class="selection">
<label >位置:</label>
<select v-model="position">
<option value="0">守门员</option>
<option value="1">前锋</option>
<option value="2">后卫</option>
</select>
</div>
<div class="selection">
<label>持杆:</label>
<select v-model="shooting_hand">
<option value="0">左手</option>
<option value="1">右手</option>
</select>
</div>
<div class="selection">
<label>风格:</label>
<select v-model="style">
<option value="写实">写实</option>
<option value="乐高">乐高</option>
<option value="国漫">国漫</option>
<option value="日漫">日漫</option>
<option value="油画">油画</option>
<option value="涂鸦">涂鸦</option>
<option value="素描">素描</option>
</select>
</div>
</div>
<div class="generate">
<button @click="generate">生成</button>
</div>
</div>
<div class="output">
<div class="generated">
<img :src="imgUrl" alt="" v-if="imgUrl"/>
<div v-if="status">{{ status }}</div>
</div>
</div>
</div>
</template>
<script setup>
import { ref, onMounted } from 'vue'
// script + setup 是vue3 最好的代码组织方式
// composition api 组合
// 直接在script setup 中定义函数
// 用于标记一个DOM 对象, 如果要做就用ref
// 未挂载前null, uploadImage tempalte 中的ref 绑定的对象
const patToken = import.meta.env.VITE_PAT_TOKEN;
console.log(patToken, '////');
const uploadUrl = `https://api.coze.cn/v1/files/upload`;
const workflowUrl = `https://api.coze.cn/v1/workflow/run`;
const workflowId = `7584046133912961067`;
const uniform_number = ref(10);
const uniform_color = ref('红');
const position = ref(0);
const shooting_hand = ref(0);
const style = ref('写实');
// 数据状态
const status = ref('');// 空 -> 上传中 -> 生成中 -> 生成成功
const imgUrl = ref(''); // 生成的图片url
// 生成图片模块
const generate = async () => {
status.value = "图片上传中..."
const file_id = await uploadFile();
if(!file_id) return ;
status.value = '图片上传成功,正在生成...'
// workflow 调用
const parameters = {
picture: JSON.stringify({
file_id // 安全问题
}),
style: style.value,
uniform_color: uniform_color.value,
uniform_number: uniform_number.value,
position: position.value,
shooting_hand: shooting_hand.value,
}
const res = await fetch(workflowUrl,{
method: 'POST',
headers: {
'Authorization': `Bearer ${patToken}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
workflow_id: workflowId,
parameters
})
})
const ret = await res.json();
if(ret.code !== 0 ){
status.value = ret.msg;
return;
}
const data = JSON.parse(ret.data);
console.log(data);
status.value = '';
imgUrl.value = data.data;
}
// 上传文件到coze服务器
const uploadFile = async () => {
// post请求体 http协议
const formdata = new FormData(); // 收集表单数据
const input = uploadImage.value;
if(!input.files || input.files.length <= 0) return;
formdata.append('file', input.files[0]); // 请求体里加上了文件
// coze发送 http 请求 上传
const res = await fetch(uploadUrl, {
method: 'POST',
headers: {
// 请求头 令牌
// 授权令牌
'Authorization': `Bearer ${patToken}`
},
body: formdata
})
const ret = await res.json();
console.log(ret);
if(ret.code !== 0) { // 如果出错了
status.value = ret.msg; // msg 错误消息
return
}
return ret.data.id;
}
// 图片预览模块
const uploadImage = ref(null);
const imgPreview = ref(''); // 申明了响应式对象
// null -> dom 对象 变化
// 挂载了
onMounted(() => {
console.log(uploadImage.value);
})
const updateImageData = () => {
// html5 文件对象
// console.log(uploadImage.value.files);
const input = uploadImage.value;
if (!input.files || input.files.length === 0) {
return;
}
const file = input.files[0]; // 文件对象 html5 新特性
console.log(file);
// FileReader 文件阅读对象
const reader = new FileReader();
reader.readAsDataURL(file); // url 异步的
reader.onload = (e) => { // 读完了
// console.log(e.target.result);
imgPreview.value = e.target.result;
}
}
</script>
<style scoped>
.container {
display: flex;
flex-direction: row;
align-items: start;
justify-content: start;
height: 100vh;
font-size: .85rem;
}
.preview {
max-width: 300px;
margin-bottom: 20px;
}
.settings {
display: flex;
flex-direction: row;
align-items: start;
justify-content: start;
margin-top: 1rem;
}
.selection {
width: 100%;
text-align: left;
}
.selection input {
width: 50px;
}
.input {
display: flex;
flex-direction: column;
min-width: 330px;
}
.file-input {
display: flex;
margin-bottom: 16px;
}
.output {
margin-top: 10px;
min-height: 300px;
width: 100%;
text-align: left;
}
button {
padding: 10px;
min-width: 200px;
margin-left: 6px;
border: solid 1px black;
}
.generate {
width: 100%;
margin-top: 16px;
}
.generated {
width: 400px;
height: 400px;
border: solid 1px black;
position: relative;
display: flex;
justify-content: center;
/* 水平居中 */
align-items: center;
/* 垂直居中 */
}
.output img {
width: 100%;
}
</style>
一、前端界面结构:用户交互的基础框架
应用的界面结构通过 Vue3 模板(template)实现,采用左右分区的布局设计,左侧为输入区,右侧为输出区,整体遵循 "操作 - 反馈" 的用户体验逻辑。
核心结构解析
- 输入区(.input) :包含文件上传组件、图片预览区、表单设置区和生成按钮。用户通过此处完成 "上传图片 - 设置参数 - 触发生成" 的全流程操作。
- 输出区(.output) :用于展示生成的冰球运动员图片及实时状态(如 "上传中"、"生成中"),是用户操作的结果反馈区域。
实现代码片段
<template>
<div class="container">
<div class="input"> <!-- 输入区:上传与设置 -->
<div class="file-input">
<input type="file" ref="uploadImage" accept="image/*" @change="updateImageData" />
</div>
<img :src="imgPreview" alt="" v-if="imgPreview"/> <!-- 预览图 -->
<div class="settings"> <!-- 表单设置 -->
<!-- 队服编号、颜色等参数 -->
</div>
<button @click="generate">生成</button>
</div>
<div class="output"> <!-- 输出区:结果展示 -->
<img :src="imgUrl" alt="" v-if="imgUrl"/>
<div v-if="status">{{ status }}</div> <!-- 状态反馈 -->
</div>
</div>
</template>
实现效果:
意义与注意事项
-
意义:清晰的分区设计降低了用户操作成本,让 "上传 - 设置 - 生成 - 查看" 的流程直观可感。
-
注意事项:
- 输入区表单元素需添加
required属性或前端验证,避免用户未上传图片或未设置参数时触发生成; - 输出区的容器尺寸(如示例中 400x400px)需考虑生成图片的比例,避免拉伸变形。
- 输入区表单元素需添加
二、图片上传与预览模块:用户体验的关键环节
图片上传是应用的起点,而预览功能则是提升用户体验的核心 —— 用户可通过预览确认上传内容,避免无效操作。
实现逻辑
- 文件选择:通过
input[type="file"]组件接收用户上传的图片,限制类型为image/*(仅图片文件)。 - 预览生成:利用 HTML5 的
FileReaderAPI 将图片文件转换为 base64 编码的字符串,赋值给imgPreview(响应式变量),再通过<img :src="imgPreview">展示。
核心代码
// 图片预览逻辑
const uploadImage = ref(null); // 绑定文件输入框DOM
const imgPreview = ref(''); // 存储预览图的base64地址
const updateImageData = () => {
const input = uploadImage.value;
if (!input.files || input.files.length === 0) return; // 无文件则退出
const file = input.files[0];
const reader = new FileReader();
reader.readAsDataURL(file); // 异步转换为base64
reader.onload = (e) => {
imgPreview.value = e.target.result; // 转换完成后更新预览图
};
意义与注意事项
-
意义:base64 预览无需等待服务器响应,实时反馈上传内容,解决了 "大文件上传时用户不知道是否操作成功" 的痛点。
-
注意事项:
FileReader.readAsDataURL是异步操作,需在onload回调中处理结果,避免同步获取导致的imgPreview为空;- 大图片的 base64 字符串可能较长,虽不影响显示,但需注意内存占用(可通过压缩图片优化,如使用
canvas压缩)。
三、表单数据收集模块:个性化生成的基础
用户通过表单设置冰球运动员的细节(如队服颜色、位置、风格等),这些参数将传递给 AI 模型,实现个性化生成。
实现逻辑
- 采用 Vue3 的
v-model双向绑定表单元素与响应式变量,实时同步用户输入。 - 支持的参数包括:队服编号(
uniform_number)、队服颜色(uniform_color)、位置(position)、持杆手(shooting_hand)、风格(style)。
核心代码
<!-- 表单部分 -->
<div class="settings">
<div class="selection">
<label>队服编号:</label>
<input type="number" v-model="uniform_number"/>
</div>
<div class="selection">
<label>队服颜色:</label>
<select v-model="uniform_color">
<option value="红">红</option>
<option value="蓝">蓝</option>
</select>
</div>
<!-- 其他参数:位置、持杆手、风格 -->
</div>
<script setup>
// 响应式变量绑定表单数据
const uniform_number = ref(10); // 默认值10
const uniform_color = ref('红'); // 默认值红
const position = ref(0); // 0=守门员,1=前锋,2=后卫
// ...其他参数
</script>
意义与注意事项
-
意义:表单参数是用户个性化需求的载体,让 AI 生成的结果更符合用户预期,提升产品实用性。
-
注意事项:
- 队服编号为数字类型,需限制输入范围(如 1-99),避免无效值(可通过
min/max属性或自定义验证); - 下拉选项的
value需与 AI 模型要求的参数格式一致(如位置用 0/1/2 而非字符串,避免模型解析错误)。
- 队服编号为数字类型,需限制输入范围(如 1-99),避免无效值(可通过
四、AI 生成核心模块:对接 Coze 工作流的实现
应用的核心功能是通过调用 Coze 工作流 API 将用户上传的图片与表单参数转化为冰球运动员形象,该模块分为 "文件上传到 Coze 服务器" 和 "调用工作流生成图片" 两个步骤。
1. 文件上传到 Coze 服务器
将用户上传的图片上传至 Coze 服务器,获取file_id(用于后续工作流调用)。
实现逻辑
- 使用
FormData封装图片文件(符合 HTTP 文件上传规范); - 通过
fetch发送 POST 请求,携带 Authorization 令牌(Bearer ${patToken})验证身份; - 成功响应后返回
file_id,失败则展示错误信息。
核心代码
const uploadUrl = `https://api.coze.cn/v1/files/upload`;
const patToken = import.meta.env.VITE_PAT_TOKEN; // 从环境变量获取令牌
const uploadFile = async () => {
const formdata = new FormData();
const input = uploadImage.value;
if (!input.files || input.files.length <= 0) return; // 校验文件存在性
formdata.append('file', input.files[0]); // 追加文件到表单数据
const res = await fetch(uploadUrl, {
method: 'POST',
headers: { 'Authorization': `Bearer ${patToken}` }, // 身份验证
body: formdata
});
const ret = await res.json();
if (ret.code !== 0) {
status.value = ret.msg; // 错误信息展示
return null;
}
return ret.data.id; // 返回file_id
};
2. 调用 Coze 工作流生成图片
使用上一步获取的file_id及表单参数,调用工作流 API 生成最终图片。
实现逻辑
- 构建包含
file_id和表单参数的请求体; - 发送 POST 请求到工作流接口,携带工作流 ID(
workflowId); - 解析响应结果,若成功则获取生成图片的 URL(
imgUrl),失败则展示错误信息。
核心代码
const workflowUrl = `https://api.coze.cn/v1/workflow/run`;
const workflowId = `7584046133912961067`; // 工作流唯一标识
const generate = async () => {
status.value = "图片上传中..."; // 更新状态
const file_id = await uploadFile();
if (!file_id) return; // 上传失败则退出
status.value = '图片上传成功,正在生成...';
// 构建参数
const parameters = {
picture: JSON.stringify({ file_id }),
style: style.value,
uniform_color: uniform_color.value,
// ...其他参数
};
const res = await fetch(workflowUrl, {
method: 'POST',
headers: {
'Authorization': `Bearer ${patToken}`,
'Content-Type': 'application/json' // JSON格式请求体
},
body: JSON.stringify({ workflow_id: workflowId, parameters })
});
const ret = await res.json();
if (ret.code !== 0) {
status.value = ret.msg;
return;
}
const data = JSON.parse(ret.data);
imgUrl.value = data.data; // 更新生成图片URL
status.value = ''; // 清空状态
};
意义与注意事项
-
意义:该模块是应用的 "大脑",通过对接 Coze 的 AI 能力,将用户输入转化为目标图片,实现核心功能。
-
注意事项:
- 令牌(
patToken)需通过环境变量(import.meta.env)管理,避免硬编码泄露(生产环境需配置.env文件并忽略版本控制); - API 调用可能存在网络延迟,需通过
status变量实时反馈进度(如 "上传中"、"生成中"),避免用户重复点击; - 需处理各种错误场景(如令牌过期、文件过大、网络中断),通过
ret.msg展示友好的错误提示。
- 令牌(
五、状态管理与用户反馈:流畅体验的保障
应用通过响应式变量管理全流程状态,为用户提供清晰的操作反馈,避免 "操作后无响应" 的困惑。
核心状态变量
imgPreview:存储上传图片的预览地址,用于前端展示;imgUrl:存储 AI 生成图片的 URL,展示最终结果;status:存储实时状态信息(如 "上传中"、"生成失败"),引导用户预期。
实现逻辑
- 基于 Vue3 的
ref创建响应式变量,状态更新时自动触发 DOM 渲染; - 在关键流程节点(如上传开始、上传成功、生成失败)更新
status,让用户了解当前进度。
意义与注意事项
-
意义:状态管理是用户体验的 "粘合剂",通过实时反馈减少用户焦虑,提升操作信心。
-
注意事项:
- 状态更新需与实际流程同步(如上传未完成时不能显示 "上传成功");
- 生成图片加载失败时(如
imgUrl无效),需补充错误处理(如显示 "图片加载失败,请重试")。
总结
"AI 应用之冰球" 通过 Vue3 实现了从用户输入到 AI 生成的完整流程,各模块分工明确:界面结构提供交互框架,图片预览提升操作确定性,表单收集个性化需求,AI 对接模块实现核心功能,状态管理保障体验流畅。
在实际开发中,需重点关注用户输入验证、API 安全与错误处理、大文件优化等细节,才能让应用在趣味性之外,兼具稳定性与易用性。该应用的设计思路也为类似 "上传 - 生成" 类 AI 应用提供了可参考的范式 —— 以用户体验为核心,通过清晰的流程与反馈,降低 AI 技术的使用门槛。