AI Agent开发系列(一)打造小红书文案生成神器:基于Deepseek、 FastAPI、LangChain 和通义万相的技术实践(3)配套的交互页面

一、项目概述

本项目是一个小红书文案生成器,用户可以输入写作主题、文章长度和背景信息,系统会生成相应的文案和图片。项目使用Vue 3和Element Plus进行前端开发,通过axios与后端API进行通信,后端API开发以及代码详见前面系列文章。

二、开发环境搭建

1. 安装Node.js和npm

Node.js是一个基于Chrome V8引擎的JavaScript运行环境,npm是Node.js的包管理工具。

  • 步骤
    • 访问Node.js官方网站,下载适合你操作系统的安装包。
    • 按照安装向导的提示完成安装。安装完成后,打开终端,输入以下命令验证安装是否成功:
node -v
npm -v

2. 创建Vue项目

使用Vue CLI创建一个新的Vue项目。

  • 步骤
    • 全局安装Vue CLI:
npm install -g @vue/cli
- 创建新项目:
vue create redbook-content-generator
- 在创建过程中,你可以选择手动配置项目,选择需要的特性,如Babel、ESLint等。

3. 安装项目依赖

进入项目目录,安装Element Plus和axios等依赖。

  • 步骤
cd redbook-content-generator
npm install element-plus axios vue-clipboard3

4. 配置Element Plus

main.js中引入并使用Element Plus。

  • 代码示例(src/main.js
// 导入Vue核心功能和根组件
import { createApp } from 'vue'
// 导入Element Plus组件库及其样式
import ElementPlus from 'element-plus'
import 'element-plus/dist/index.css'
// 导入根组件
import App from './App.vue'

// 创建Vue应用实例
const app = createApp(App)

// 注册Element Plus组件库
app.use(ElementPlus)
// 将应用挂载到DOM元素上
app.mount('#app')

三、项目开发

1. 编写组件模板

App.vue中编写组件模板,包括表单输入、结果展示等部分。

  • 代码示例(src/App.vue部分核心代码)
<template>
  <div class="container">
    <el-card class="input-card">
      <template #header>
        <div class="card-header">
          <h2>小红书文案生成器</h2>
        </div>
      </template>
      
      <el-form :model="form" label-position="top">
        <el-form-item label="写作主题">
          <el-input v-model="form.subject" placeholder="请输入写作主题" type="textarea" :rows="2" />
        </el-form-item>
        <!-- 其他表单输入项 -->
        <el-form-item>
          <el-button type="primary" :loading="loading" @click="generateContent">生成内容</el-button>
        </el-form-item>
      </el-form>
    </el-card>

    <el-card v-if="workflowResult" class="result-card">
      <template #header>
        <div class="card-header">
          <h3>生成结果</h3>
          <div class="status-tag">
            <el-tag :type="statusType">{{ statusText }}</el-tag>
          </div>
        </div>
      </template>

      <!-- 文章内容 -->
      <div v-if="workflowResult.content" class="content-section">
        <h4>文章内容 <el-button type="primary" link @click="copyContent(workflowResult.content)">复制</el-button></h4>
        <div class="content-text" v-html="formattedContent"></div>
      </div>
      <!-- 封面文案、图片展示等部分 -->
    </el-card>
  </div>
</template>

2. 编写脚本逻辑

<script setup>标签中编写脚本逻辑,包括数据定义、计算属性和方法。

  • 代码示例(src/App.vue部分核心代码)
<script setup>
import { ref, computed } from 'vue'
import { ElMessage } from 'element-plus'
import { Picture } from '@element-plus/icons-vue'
import axios from 'axios'
import useClipboard from 'vue-clipboard3'

const { toClipboard } = useClipboard()

const form = ref({
  subject: '',
  length: '',
  context: ''
})

const loading = ref(false)
const workflowResult = ref(null)
const workflowId = ref(null)
const pollingInterval = ref(null)

const statusType = computed(() => {
  if (!workflowResult.value) return ''
  const status = workflowResult.value.status
  switch (status) {
    case 'started':
    case 'processing':
      return 'warning'
    case 'completed':
      return 'success'
    case 'error':
      return 'danger'
    default:
      return 'info'
  }
})

const statusText = computed(() => {
  if (!workflowResult.value) return ''
  const status = workflowResult.value.status
  switch (status) {
    case 'started':
      return '已开始'
    case 'processing':
      return '处理中'
    case 'completed':
      return '已完成'
    case 'error':
      return '出错'
    default:
      return status
  }
})

const formattedContent = computed(() => {
  if (!workflowResult.value?.content) return ''
  return workflowResult.value.content.replace(/\n/g, '<br>')
})

// 封装带重试机制的axios请求函数
const axiosWithRetry = async (config, retries = 3) => {
  try {
    return await axios(config)
  } catch (error) {
    if (retries > 0 && (error.code === 'ECONNABORTED' || !error.response)) {
      await new Promise(resolve => setTimeout(resolve, 1000))
      return axiosWithRetry(config, retries - 1)
    }
    throw error
  }
}

// 生成内容的主要函数
const generateContent = async () => {
  if (!form.value.subject) {
    ElMessage.warning('请输入写作主题')
    return
  }

  try {
    loading.value = true
    const response = await axiosWithRetry({
      method: 'POST',
      url: 'http://localhost:8001/workflow/start',
      data: form.value,
      timeout: 10000,
      headers: {
        'Content-Type': 'application/json'
      }
    })
    workflowResult.value = response.data
    workflowId.value = response.data.workflow_id

    startPolling()
  } catch (error) {
    let errorMessage = '启动工作流失败'
    if (error.code === 'ECONNABORTED') {
      errorMessage = '请求超时,请检查网络连接'
    } else if (!error.response) {
      errorMessage = '网络连接失败,请检查网络设置'
    } else {
      errorMessage += ':' + (error.response?.data?.detail || error.message)
    }
    ElMessage.error(errorMessage)
  } finally {
    loading.value = false
  }
}

// 开始轮询工作流状态的函数
const startPolling = () => {
  if (pollingInterval.value) {
    clearInterval(pollingInterval.value)
  }

  let failedAttempts = 0
  const MAX_POLLING_FAILURES = 3

  pollingInterval.value = setInterval(async () => {
    try {
      const response = await axiosWithRetry({
        method: 'GET',
        url: `http://localhost:8001/workflow/${workflowId.value}`,
        timeout: 5000,
      })
      workflowResult.value = response.data
      failedAttempts = 0

      if (['completed', 'error'].includes(response.data.status)) {
        clearInterval(pollingInterval.value)
      }
    } catch (error) {
      failedAttempts++
      console.error('轮询工作流状态失败:', error)
      
      if (failedAttempts >= MAX_POLLING_FAILURES) {
        clearInterval(pollingInterval.value)
        ElMessage.error('网络连接不稳定,请刷新页面重试')
        workflowResult.value = {
          status: 'error',
          error: '网络连接中断,请刷新页面重试'
        }
      }
    }
  }, 2000)
}

// 复制文章内容到剪贴板
const copyContent = async (content) => {
  try {
    await toClipboard(content)
    ElMessage.success('复制成功')
  } catch (error) {
    ElMessage.error('复制失败')
  }
}

// 复制封面文案
const copyCoverText = async () => {
  if (!workflowResult.value?.cover_text) return
  
  const coverText = [
    `主标题:${workflowResult.value.cover_text.main_title}`,
    `副标题:${workflowResult.value.cover_text.subtitle}`,
    `点缀文案:${workflowResult.value.cover_text.decorative_text}`,
    `标签:${workflowResult.value.cover_text.tags.join(' ')}`
  ].join('\n')

  try {
    await toClipboard(coverText)
    ElMessage.success('复制成功')
  } catch (error) {
    ElMessage.error('复制失败')
  }
}

// 下载图片
const downloadImage = async (url) => {
  try {
    const response = await axios({
      url,
      method: 'GET',
      responseType: 'blob'
    })

    const blob = new Blob([response.data])
    const downloadUrl = window.URL.createObjectURL(blob)
    const link = document.createElement('a')
    link.href = downloadUrl
    link.download = `image_${Date.now()}.png`
    document.body.appendChild(link)
    link.click()
    document.body.removeChild(link)
    window.URL.revokeObjectURL(downloadUrl)

    ElMessage.success('下载成功')
  } catch (error) {
    ElMessage.error('下载失败')
  }
}
</script>

四、项目部署

1. 构建项目

使用Vue CLI提供的命令将项目打包构建。

  • 步骤
npm run build

执行该命令后,会在项目根目录下生成一个dist文件夹,里面包含了打包后的静态文件。

2. 部署到服务器

dist文件夹中的文件部署到服务器上。

  • 步骤
    • dist文件夹上传到服务器,可以使用FTP工具或Git等。
    • 配置服务器的Web服务器(如Nginx或Apache),将域名或IP指向dist文件夹。
Nginx配置示例
server {
    listen 80;
    server_name your_domain_or_ip;

    root /path/to/your/dist;

    index index.html;

    location / {
        try_files $uri $uri/ /index.html;
    }
}
Apache配置示例

在Apache的配置文件中添加以下内容:

<VirtualHost *:80>
    ServerName your_domain_or_ip
    DocumentRoot /path/to/your/dist

    <Directory /path/to/your/dist>
        Options Indexes FollowSymLinks
        AllowOverride All
        Require all granted
    </Directory>

    ErrorLog ${APACHE_LOG_DIR}/error.log
    CustomLog ${APACHE_LOG_DIR}/access.log combined
</VirtualHost>

3. 启动服务器

启动Nginx或Apache服务器,使项目可以通过域名或IP访问。

  • Nginx启动命令
sudo systemctl start nginx
  • Apache启动命令
sudo systemctl start apache2

五、总结

通过以上步骤,你可以完成小红书文案生成器项目的开发和部署。在开发过程中,要注意代码的模块化和可维护性;在部署过程中,要确保服务器的配置正确,以保证项目的正常运行。

### 易于动画 (EasyAnimate) 通义的特点及适用场景 #### 特点分析 易于动画 (EasyAnimate)[^1] 是种专注于视频生成增强的技术工具,其核心功能在于通过先进的算法优化现有模型的表现力。它能够显著提升由其他基础模型(如 HunYuan 或 CogVideox)生成的视频质量。然而,它的局限性在于缺乏原生支持某些特定框架的功能扩展。 比之下,通义种多模态人工智能技术,具有更广泛的用途。它可以处理图像、文本等多种数据形式,并能实现跨模态的任务转换。例如,在给定段描述性的文字输入时,通义可以自动生成高质量的艺术风格图片或者动态效果[^2]。 #### 技术优势对比 - **灵活性与定制化能力** - 对于需要高度专业化调整的应用场合来说,EasyAnimate 提供了更多针对具体需求进行微调的可能性。比如可以通过 LoRA 训练方法来适配个性化特征的数据集,从而获得更加贴合目标领域特性的输出结果。 - **用户体验友好度** - 而从操作简便性易用性角度来看,则可能因个人偏好而异。部分用户可能会觉得通义界面设计直观简单,适合初学者快速上手;而对于熟悉编程环境的人来说,利用 Python API 接口直接调用 EasyAnimate 功能或许更为高效快捷[^3]。 ```python # 示例代码展示如何加载预训练权重并设置超参数以改进性能 from transformers import AutoModelForSeq2SeqLM, TrainingArguments, Trainer model_name_or_path = "your_model_checkpoint" training_args = TrainingArguments(output_dir="./results", num_train_epochs=5) def model_init(): return AutoModelForSeq2SeqLM.from_pretrained(model_name_or_path) trainer = Trainer( args=training_args, train_dataset=train_data, eval_dataset=val_data, model_init=model_init ) ``` #### 适用场景划分 基于上述特性差异可以看出: - 如果项目主要围绕着复杂视觉内容创作展开,并且追求极致画质呈现的话,那么采用 EasyAnimate 将会是个明智的选择; - 反之当面临资源有限但又希望兼顾多种媒体形态表达的需求情境下,则推荐选用综合能力强悍同时也对容易部署实施的通义解决方案[^4]。 ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

李半仙

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值