iT邦幫忙

2025 iThome 鐵人賽

DAY 29
0
自我挑戰組

跟 AI Agent 變成好朋友系列 第 29

【Day29】AI Agent 魔法詠唱 - 前端整合

  • 分享至 

  • xImage
  •  

本次專案, 前端(Vue) 主要透過 API 與後端互動,來顯示🥤飲品菜單、🤖 AI 智能推薦助手、⭐ 收藏喜愛飲品等內容,並根據回傳資料更新畫面。以下以🤖 AI 智能推薦功能為例,說明如何設計前端與後端互動流程。

  1. 首先,需要取得與設定使用者資訊、API BASE URL、載入服務狀態。
// 使用者資訊
const username = ref('TestUser') // 實際會從 AWS Cognito 取得

// API BASE URL
const API_BASE_URL = 'http://localhost:8080/api'

// 生命週期
onMounted(() => {
  // 載入服務狀態
  fetchServiceStatus()
})

// 取得服務狀態
const serviceStatus = ref(null)
const fetchServiceStatus = async () => {
  try {
    const response = await fetch(`${API_BASE_URL}/recommendations/service-status`)
    if (response.ok) {
      const data = await response.json()
      serviceStatus.value = data
      console.log('服務狀態:', data)
    }
  } catch (error) {
    console.error('獲取服務狀態失敗:', error)
  }
}
  1. 前端會使用 fetch 發送 POST 請求到後端 API(/recommendations/generate),並給予 userInput 和 username參數,讓後端回傳 JSON,前端根據 status 判斷成功或失敗,並將結果(推薦、心情、AI 回應等)推送到 messages。在沒有推薦結果或 API 錯誤的情況下,前端會顯示提示或錯誤訊息。整個流程以 isLoading 控制狀態,避免重複請求並優化使用者體驗。
// 前端與後端互動流程(以AI 智能推薦為例)
const messages = ref([])
const isLoading = ref(false)
let messageId = 0
const userInput = ref('')

const sendMessage = async () => {
  if (!userInput.value.trim() || isLoading.value) return

  const messageContent = userInput.value.trim()
  userInput.value = ''

  // 顯示使用者資訊
  messages.value.push({
    id: messageId++,
    type: 'user',
    content: messageContent,
    timestamp: new Date(),
  })

  await scrollToBottom()

  // 發送 POST 請求到後端生成推薦內容 
  isLoading.value = true

  try {
    const response = await fetch(`${API_BASE_URL}/recommendations/generate`, {
      method: 'POST',
      headers: {
        'Content-Type': 'application/json',
      },
      body: JSON.stringify({
        userInput: messageContent,
        username: username.value,
      }),
    })

    if (!response.ok) {
      throw new Error(`HTTP error! status: ${response.status}`)
    }

    const data = await response.json()

    if (data.status === 'error') {
      // 錯誤
      messages.value.push({
        id: messageId++,
        type: 'ai',
        error: data.message || '發生未知錯誤',
        timestamp: new Date(),
      })
    } else if (data.status === 'success') {
      // 成功 - 檢查必要欄位
      const moodDetected = data.moodDetected || data.mood || '未檢測到心情'
      const aiResponse = data.aiResponse || data.response || data.message || 'AI 正在為您分析...'
      const aiReason = data.aiReason || 'AI 為您精心挑選的飲品推薦'
      const recommendations = Array.isArray(data.recommendations) ? data.recommendations : []

      // 如果沒有推薦結果
      if (recommendations.length === 0) {
        console.warn('沒有收到推薦結果')
      }

      messages.value.push({
        id: messageId++,
        type: 'ai',
        moodDetected: moodDetected,
        aiResponse: aiResponse,
        aiReason: aiReason,
        recommendations: recommendations,
        serviceType: data.serviceType || '未知服務',
        isBedrockAvailable: data.isBedrockAvailable || false,
        timestamp: new Date(),
      })

      console.log('處理後的 AI 回應:', {
        moodDetected: moodDetected,
        aiResponse: aiResponse,
        aiReason: aiReason,
        recommendations: recommendations,
        serviceType: data.serviceType,
        isBedrockAvailable: data.isBedrockAvailable,
      })
    } else {
      // 未知
      console.error('未知的格式:', data)
      messages.value.push({
        id: messageId++,
        type: 'ai',
        error: '回應格式異常,請稍後再試',
        timestamp: new Date(),
      })
    }
  } catch (error) {
    console.error('API 請求失敗:', error)
    messages.value.push({
      id: messageId++,
      type: 'ai',
      error: '網路連線錯誤,請稍後再試',
      timestamp: new Date(),
    })
  } finally {
    isLoading.value = false
    await scrollToBottom()
  }
}
  1. 前端根據 API 回應的資料,動態渲染檢測心情、推薦引擎、推薦結果或錯誤訊息等:
<div v-else class="message-content">
  <div
    v-if="message.moodDetected && message.moodDetected !== '未檢測到心情'"
    class="mood-info"
  >
    <span class="mood-label">🔍 檢測心情:</span>
    <span class="mood-value">{{ message.moodDetected }}</span>
  </div>

  <div v-if="message.serviceType" class="service-info">
    <span class="service-label">🤖 推薦引擎:</span>
    <span
      class="service-value"
      :class="{ bedrock: message.isBedrockAvailable, local: !message.isBedrockAvailable }"
    >
      {{ message.serviceType }}
    </span>
  </div>

  <div
    v-if="message.aiResponse && message.aiResponse !== 'AI 正在為您分析...'"
    class="ai-response"
  >
    {{ message.aiReason }}
  </div>

  <!-- 當沒有有效的 AI 回應時顯示預設資訊 -->
  <div v-else-if="!message.error" class="ai-response">
    <div class="analyzing-info">
      <span class="analyzing-icon">🔍</span>
      <span>正在分析您的需求,請稍候...</span>
    </div>
  </div>

  <div
    v-if="message.recommendations && message.recommendations.length > 0"
    class="recommendations"
  >
    <h4>🍹 為您推薦:</h4>
    <div class="recommendation-list">
      <div
        v-for="rec in message.recommendations"
        :key="rec.name"
        class="recommendation-item"
      >
        <div class="rec-header">
          <span class="rec-name">{{ rec.name }}</span>
          <span class="rec-category">{{ rec.category }}</span>
          <span v-if="rec.price" class="rec-price">${{ rec.price }}</span>
        </div>
        <div class="rec-description">{{ rec.description }}</div>
        <div class="rec-reason">{{ rec.reason }}</div>
        <div class="rec-actions">
          <button @click="addToFavorites(rec)" class="fav-btn" :disabled="isAddingFavorite">
            <span v-if="isAddingFavorite">⏳</span>
            <span v-else>⭐ 收藏</span>
          </button>
          <span class="match-score">匹配度: {{ Math.round(rec.matchScore * 100) }}%</span>
        </div>
      </div>
    </div>
  </div>

  <!-- 當沒有推薦結果時預設資訊 -->
  <div v-else-if="!message.error && message.serviceType" class="no-recommendations">
    <div class="no-rec-info">
      <span class="no-rec-icon">💭</span>
      <p>目前沒有找到合適的推薦,請嘗試描述更具體的需求或心情。</p>
      <div class="suggestion-tips">
        <strong>💡 建議:</strong>
        <ul>
          <li>描述您的心情狀態(如:開心、疲憊、放鬆)</li>
          <li>提及偏好的口味(如:甜的、酸的、清爽的)</li>
          <li>說明場合或時間(如:上班時、運動後、睡前)</li>
        </ul>
      </div>
    </div>
  </div>

  <div v-if="message.error" class="error-message">❌ {{ message.error }}</div>
</div>

當然,其他的互動功能(例如 ⭐ 收藏喜愛飲品、💾 推薦歷史記錄等)同樣以 API 請求方式與後端溝通,並根據回應更新前端狀態。

這樣的設計能使前端與後端的資料流更加清晰,讓每個互動功能都能獨立維護。前端只需根據 API 回應動態更新狀態,即可快速響應使用者的操作,透過維持資料格式的一致性,同時兼顧使用者體驗。


上一篇
【Day28】AI Agent 魔法詠唱 - Controller (API 控制器)
下一篇
【Day30】AI Agent 魔法詠唱 - 結語
系列文
跟 AI Agent 變成好朋友30
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言