让宠物打冰球!手把手教你用 Coze 多模态工作流 + Vue 3 打造 AI 拟人生成器

让你的宠物闪耀橄榄球场!

上传一张照片,选择号码与位置,AI 即刻生成你家猫狗身穿战袍、驰骋冰场的拟人化形象。本文将带你用 Coze 多模态工作流 + Vue 3,从零打造这个趣味 AI 应用。

1. 构建你的AI COZE 工作流

具体关于 Coze 账号注册、工作流创建及 PAT Token 获取等操作步骤,可参考我的指南:

从 TRAE 脚手架 到 Coze 智能体 :打造支持 RAG 的编程教育客服系统 掘金

Coze 智能体创建步骤指南

完成iceball_player工作流的设计

开始节点:定义输入接口(图片必填,其他可选)

在设计工作流的「开始节点」时,需明确数据接口结构:图片输入是必填项,而其他参数(如球衣号码、场上位置、风格类型等)可根据实际需求自由定义和扩展。

代码节点:参数校验与默认值填充

在工作流中添加一个代码节点,用编程逻辑对传入的参数进行校验:若用户未提供某项输入(如球衣号码、位置或风格),则自动赋予合理的默认值,确保后续流程稳定运行。

添加校验和加入默认值的代码

代码部分

typescript 复制代码
const random = (start: number, end: number) => {
    const p = Math.random();
    return Math.floor(start * (1 - p) + end * p);
}

async function main({ params }: Args): Promise<Output> {
    if (params.position == null) params.position = random(0, 3);
    if (params.shooting_hand == null) params.shooting_hand = random(0, 2);

    const style = params.style || '写实';
    const uniform_number:string = (params.uniform_number || 10).toString();
    const uniform_color = params.uniform_color || '红';
    const position = params.position  == 0 ? '守门员': (params.position == 1 ? '前锋': '后卫');
    const shooting_hand = params.shooting_hand == 0 ? '左手': '右手';
    const empty_hand = params.shooting_hand ? '左手': '右手';

    // 构建输出对象
    const ret = {
        style,
        uniform_number,
        uniform_color,
        position,
        shooting_hand,
    };

    return ret;
}

图片理解节点:解析宠物视觉特征

这个节点用于分析用户上传的宠物图片,提取其关键视觉特征(如品种、毛色、面部朝向、体型等),为后续生成拟人化冰球运动员形象提供语义依据。

特征提取节点:结构化关键属性

该节点并不直接处理图像,而是基于前一个 imgUnderstand_1 节点输出的文本描述(例如"一只橘色短毛猫,正面朝向,大眼睛,表情慵懒"),从中结构化地提取关键宠物特征,如品种、毛色、姿态、面部特征等。这些提取出的字段将作为后续生成环节的精细化控制参数,使 AI 输出更贴合宠物的真实形象。

提示词

ini 复制代码
你是动物学家,负责丛动物描述中,提取出该动物(主要是外表)里最有图特性的特征,例如特征的肤色、表情、神态、动作等等。

图像生成节点:融合提示词生成拟人化形象

图像生成步骤是整个流程的核心,它依赖于之前节点处理得到的各种风格偏好、图片理解内容、特征提取信息以及正向和负向提示词。这些输入共同作用,以创建最终的图像作品。

首先,需要考虑从前面代码节点获取的风格倾向,这决定了图像的整体艺术风格。接着,利用图片理解节点提供的主题描述来定义图像的基本场景。然后,根据特征节点提供的细节信息调整图像的具体元素。最后,通过正向提示词突出期望的视觉效果,并用负向提示词避免不想要的特性。结合所有这些要素,即可生成理想的图像。

正向提示词

txt 复制代码
用动物的形象和特征,将该动物**拟人**为一名宠物儿童冰球员,生成{{style}}风格的冰球球员照片,球员身穿{{uniform_color}}色队服,佩戴同色的冰球头盔,队服号码为{{uniform_number}}号,球员位置是{{position}},用{{shooting_hand}}握着球杆,另一只手空着。该照片图像风格为{{style}}。

# 动物形象描述
{{description}}

# 独特外貌特征
{{details}}

# 注意
- 照片中应强化动物独特的外貌特征,以增加辨识度
- 如果球员位置是守门员,画面中应该有冰球球门

负向提示词

txt 复制代码
球员双手各握一根球杆
球员未佩戴头盔
球员吃东西
画面中出现除了冰球之外的其他球类
地点不在冰球赛场
球员四足站立

总结

至此,我们已完成「萌宠冰球运动员生成器」工作流的核心搭建:从用户上传图片,到理解内容、提取特征、填充默认参数,再到融合风格与提示词完成最终图像生成。每一步都环环相扣------前端负责体验,工作流负责智能。这不仅是一个趣味 AI 应用的实现,更是一套可复用的"前端 + Coze 多模态工作流"开发范式。接下来,只需将工作流 ID 与前端对接,就能让每一只萌宠闪耀 NHL 冰场!

工作流预览 最终结果

二、搭建 Vue 3 前端交互界面

为了让用户能轻松上传宠物照片并生成专属冰球明星,我们需要构建一个直观、响应迅速的前端界面。整个过程分为几个清晰的步骤,每一步都为最终体验服务。

第一步:用 Vite 快速初始化 Vue 3 项目

我们从零开始,使用 Vite 官方脚手架创建一个轻量级 Vue 3 应用:

bash 复制代码
npm init vite

在交互提示中:

  • Project name :输入项目名称(如 pet-iceball
  • Framework :选择 Vue
  • Variant :选择 JavaScript(也可选 TypeScript,本文以 JS 为例)

创建完成后,进入目录并安装依赖:

bash 复制代码
cd pet-iceball
npm install

此时项目结构简洁清晰:

text 复制代码
src/
├── main.js      // 应用入口
├── App.vue      // 根组件
└── style.css    // 全局样式

运行 npm run dev,若浏览器成功打开默认欢迎页,说明开发环境已就绪!


第二步:实现图片上传与即时本地预览

用户上传图片后若没有视觉反馈,很容易误以为"没反应"。因此,我们在调用 AI 之前,先实现纯前端的图片预览------让用户立刻看到自己选的是哪只毛孩子!

App.vue 中搭建基础 UI

html 复制代码
<template>
  <div class="container">
    <div class="input-section">
      <input 
        type="file" 
        ref="uploadImage" 
        accept="image/*"
        @change="updateImageData" 
      />
      <img :src="imgPreview" alt="预览图" v-if="imgPreview" class="preview" />
    </div>
  </div>
</template>

使用 FileReader 实现预览逻辑

js 复制代码
<script setup>
import { ref } from 'vue'

const uploadImage = ref(null)
const imgPreview = ref('')

const updateImageData = () => {
  const input = uploadImage.value
  if (!input?.files?.length) return

  const file = input.files[0]
  const reader = new FileReader()
  reader.readAsDataURL(file)
  reader.onload = (e) => {
    imgPreview.value = e.target.result // 触发 Vue 响应式更新
  }
}
</script>

为什么有效?

  • FileReader.readAsDataURL() 将文件转为 Base64 字符串,可直接用于 <img> 标签;
  • imgPreview 是响应式变量,赋值后 Vue 自动更新视图;
  • 整个过程不涉及网络请求,秒级响应,体验流畅。

第三步:用响应式状态统一管理用户反馈

为了让用户清楚知道当前处于"上传中"、"生成中"还是"出错了",我们引入两个核心状态:

js 复制代码
const status = ref('')     // 显示操作状态或错误信息
const resultUrl = ref('')  // 存放 Coze 返回的生成图片 URL

并在模板中绑定:

html 复制代码
<template>
  <!-- ... -->
  <button @click="generate" :disabled="!imgPreview">生成冰球明星</button>
  <p v-if="status" class="status">{{ status }}</p>

  <div class="output" v-if="resultUrl">
    <img :src="resultUrl" alt="AI 生成结果" class="result-image" />
  </div>
</template>

这样,无论后台处理耗时多久,用户都能获得明确反馈,避免"点击无响应"的困惑。


第四步:安全、规范地调用 Coze 工作流

要让前端真正驱动 AI 生成,必须严格遵循 Coze 的 API 调用流程:先上传文件获取 file_id,再触发工作流

1. 配置 PAT Token(安全第一!)

在项目根目录创建 .env.local 文件:

env 复制代码
VITE_PAT_TOKEN=your_coze_pat_token_here

⚠️ 务必将其加入 .gitignore,防止泄露!

Vite 会自动将 VITE_ 开头的变量注入客户端代码。

在代码中读取:

js 复制代码
const patToken = import.meta.env.VITE_PAT_TOKEN

2. 上传图片到 Coze 文件服务

js 复制代码
const uploadFile = async () => {
  status.value = '图片上传中...'
  const file = uploadImage.value?.files?.[0]
  if (!file) return null

  const formData = new FormData()
  formData.append('file', file) // 字段名必须为 'file'

 try {
    const res = await fetch('https://api.coze.cn/v1/files/upload', {
      method: 'POST',
      headers: { Authorization: `Bearer ${patToken}` },
      body: formData
    })

    const data = await res.json()
    if (data.code !== 0) throw new Error(data.msg || '上传失败')
    return data.data.id // 返回 Coze 分配的 file_id
  } catch (err) {
    status.value = err.message
    return null
  }
}

3. 调用工作流生成图像

Js 复制代码
const generate = async () => {
  const fileId = await uploadFile()
  if (!fileId) return

  status.value = '正在生成冰球明星...'

  // 构造参数对象,字段名必须与工作流「开始节点」完全一致
  const parameters = {
    picture: JSON.stringify({ file_id: fileId }), // 注意:必须是字符串化的 JSON
    style: style.value,
    uniform_number: uniform_number.value,
    uniform_color: uniform_color.value,
    position: position.value,
    shooting_hand: shooting_hand.value
  }

  const res = await fetch('https://api.coze.cn/v1/workflow/run', {
    method: 'POST',
    headers: {
      Authorization: `Bearer ${patToken}`,
      'Content-Type': 'application/json'
    },
    body: JSON.stringify({
      workflow_id: 'your_workflow_id_here', // 替换为你的实际 ID
      parameters
    })
  })

  const ret = await res.json()
  if (ret.code !== 0) {
    status.value = ret.msg
    return
  }

  // Coze 的 data 字段是 JSON 字符串,需二次解析
  const result = JSON.parse(ret.data)
  resultUrl.value = result.data // 最终图片 URL
  status.value = ''
}

第五步:绑定用户自定义选项

为了让用户自由定制冰球明星形象,我们在 <script setup> 中声明所有可配置项:

js 复制代码
const uniform_number = ref(10)
const uniform_color = ref('红')
const position = ref(0)        // 0:守门员, 1:前锋, 2:后卫
const shooting_hand = ref(0)   // 0:左手, 1:右手
const style = ref('写实')

然后在模板中添加对应的 <select><input> 元素(例如):

html 复制代码
<select v-model="position">
  <option :value="0">守门员</option>
  <option :value="1">前锋</option>
  <option :value="2">后卫</option>
</select>

这些响应式变量会自动同步到 parameters 中,确保用户的选择准确传递给 Coze 工作流。

全部代码:

vue 复制代码
<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 挂载后是DOM对象 template 中的ref绑定的对象

  const patToken = import.meta.env.VITE_PAT_TOKEN
  const uploadUrl = 'https://api.coze.cn/v1/files/upload'
  const workflowUrl = 'https://api.coze.cn/v1/workflow/run'
  const workflow_id = '7584046346609917971'
  const uniform_number = ref(10)//队服编号
  const uniform_color = ref('red')//队服颜色
  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 = "图片上传成功,正在生成中..."

      //woekflow调用
      const parameters = {
        picture:JSON.stringify({
          file_id//安全问题
        }),
        style:style.value,
        uniform_number:uniform_number.value,
        uniform_color:uniform_color.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,
          parameters
        })
      })
      const ret = await res.json();
      if(ret.code !== 0){//如果出错了
        status.value = ret.msg;//msg 错误消息
        return
      }
      const data = JSON.parse(ret.data);
      console.log(ret.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[0]);
    const input = uploadImage.value
    if(!input.files || input.files[0] === 0){
          return
    }
    const file = input.files[0]//文件对象 html5 新特性
    console.log(file);
    //FileReader 文件读取对象
    const reader = new FileReader();
    reader.readAsDataURL(file);//url 异步的
    reader.onload = (e) => {//读完了 
      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>

通过以上步骤,我们完成了从前端交互到 AI 能力调用的完整链路。整个过程既保证了用户体验的流畅性,又严格遵循了 Coze 的 API 规范,为后续功能扩展(如保存历史、分享结果等)奠定了坚实基础。

总结

这个"萌宠冰球运动员生成器"看似充满魔法,实则建立在一套清晰、可复现的开发逻辑之上。我们没有一上来就对接 AI,而是采用 "先体验,再能力;先本地,再云端" 的渐进式策略,把复杂问题拆解为四个关键阶段,每一步都稳扎稳打:

  1. 从骨架开始:用 Vite + Vue 3 快速搭建一个现代化前端项目,利用 Composition API 和响应式系统,为后续功能提供干净、可维护的基础。
  2. 让用户立刻看到反馈 :在调用任何 AI 之前,先实现本地图片预览。借助 FileReader,用户一选图,界面马上响应------这种"所见即所得"的体验,是建立信任的第一步。
  3. 用状态驱动一切 :通过 ref() 声明 statusimgUrl 等核心状态,让 UI 自动跟随数据变化。无论是加载提示、错误信息还是最终结果,都由数据说了算,逻辑清晰、维护简单。
  4. 安全、规范地接入 AI :严格遵循 Coze 的 API 要求------先用 FormData 上传文件获取 file_id,再以正确格式调用工作流,并对嵌套的 JSON 响应做双重解析。每一步都考虑了认证、格式、错误兜底,确保链路可靠。

这套方法论的价值远不止于本项目。它提供了一种通用模式:先构建直观的前端交互,再逐步注入 AI 能力。既降低了开发门槛,又保障了用户体验的连贯性与可控感。

最终,你不仅得到了一只穿着冰球战袍的橘猫,更掌握了一条通往"低代码 + 高创意"AI 应用的可行路径。

相关推荐
有点笨的蛋2 小时前
Vue3 项目:宠物照片变身冰球运动员的 AI 应用
前端·vue.js
老华带你飞3 小时前
学生请假管理|基于springboot 学生请假管理系统(源码+数据库+文档)
java·前端·数据库·vue.js·spring boot·后端·spring
一 乐3 小时前
校务管理|基于springboot + vueOA校务管理系统(源码+数据库+文档)
java·数据库·vue.js·spring boot·后端·spring
用户4099322502123 小时前
Vue3中v-show如何通过CSS修改display属性控制条件显示?与v-if的应用场景该如何区分?
前端·javascript·vue.js
计算机毕设VX:Fegn08954 小时前
计算机毕业设计|基于springboot + vue旅游信息推荐系统(源码+数据库+文档)
java·数据库·vue.js·spring boot·课程设计·旅游
古月฿5 小时前
大学生素质测评系统设计与实现
java·vue.js·redis·mysql·spring·毕业设计
码农秋5 小时前
Element Plus DatePicker 日期少一天问题:时区解析陷阱与解决方案
前端·vue.js·elementui·dayjs
源码获取_wx:Fegn08955 小时前
基于springboot + vue物业管理系统
java·开发语言·vue.js·spring boot·后端·spring·课程设计
2022.11.7始学前端6 小时前
第二十二课 专属你的小说智能问答助手
工作流·coze