AI 应用之冰球:对大模型应用的小尝试

70 阅读9分钟

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>

实现效果:

1766218831670_0033fabc7d634ccbacec0c64bfbbeb29.png

意义与注意事项

  • 意义:清晰的分区设计降低了用户操作成本,让 "上传 - 设置 - 生成 - 查看" 的流程直观可感。

  • 注意事项

    • 输入区表单元素需添加required属性或前端验证,避免用户未上传图片或未设置参数时触发生成;
    • 输出区的容器尺寸(如示例中 400x400px)需考虑生成图片的比例,避免拉伸变形。

二、图片上传与预览模块:用户体验的关键环节

图片上传是应用的起点,而预览功能则是提升用户体验的核心 —— 用户可通过预览确认上传内容,避免无效操作。

实现逻辑

  1. 文件选择:通过input[type="file"]组件接收用户上传的图片,限制类型为image/*(仅图片文件)。
  2. 预览生成:利用 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 而非字符串,避免模型解析错误)。

四、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 技术的使用门槛。