websim 到 codepen 為何失敗原因分析 (API 路徑錯誤)

 



<!DOCTYPE html>

<html lang="en">

<head>

  <meta charset="UTF-8">

  <meta name="viewport" content="width=device-width, initial-scale=1.0">

  <title>AI Text Generator</title>

  <style>

    /* 基本 CSS 設定 */

    body {

      font-family: Arial, sans-serif;

      background-color: #f4f4f4;

      margin: 0;

      padding: 20px;

    }

    .container {

      max-width: 800px;

      margin: 0 auto;

      background-color: #fff;

      padding: 20px;

      border-radius: 8px;

      box-shadow: 0 2px 8px rgba(0,0,0,0.1);

    }

    .translation-area {

      display: flex;

      flex-direction: column;

      gap: 20px;

      margin-bottom: 20px;

    }

    .input-section,

    .output-section {

      display: flex;

      flex-direction: column;

      gap: 10px;

    }

    textarea {

      width: 100%;

      height: 100px;

      padding: 10px;

      border: 1px solid #ccc;

      border-radius: 4px;

      resize: vertical;

    }

    .model-select {

      width: 100%;

      padding: 8px;

      border: 1px solid #ccc;

      border-radius: 4px;

    }

    .translate-btn {

      padding: 10px 20px;

      background-color: #007BFF;

      border: none;

      color: #fff;

      border-radius: 4px;

      cursor: pointer;

    }

    .translate-btn:disabled {

      background-color: #aaa;

    }

    .attribution {

      font-size: 0.9em;

      color: #555;

    }

    .disclaimer-modal {

      position: fixed;

      top: 0;

      left: 0;

      right: 0;

      bottom: 0;

      background-color: rgba(0,0,0,0.5);

      display: none;

      justify-content: center;

      align-items: center;

    }

    .disclaimer-content {

      background-color: #fff;

      padding: 20px;

      border-radius: 8px;

      max-width: 400px;

      text-align: center;

    }

    .disclaimer-buttons {

      display: flex;

      justify-content: space-around;

      margin-top: 20px;

    }

    .disclaimer-btn {

      padding: 10px 20px;

      border: none;

      border-radius: 4px;

      cursor: pointer;

    }

    .accept-btn {

      background-color: #28a745;

      color: #fff;

    }

    .reject-btn {

      background-color: #dc3545;

      color: #fff;

    }

  </style>

</head>

<body>

  <div class="container">

    <h1>AI Text Generator</h1>

    

    <div class="translation-area">

      <div class="input-section">

        <select id="modelSelect" class="model-select">

          <option value="ChatGPT">ChatGPT</option>

          <option value="Google Gemini">Google Gemini</option>

          <option value="Claude">Claude</option>

          <option value="DALL-E">DALL-E</option>

          <option value="GPT-4">GPT-4</option>

        </select>

        <textarea id="inputText" placeholder="Enter your prompt here..."></textarea>

      </div>


      <div class="output-section">

        <textarea id="outputText" placeholder="Generated text will appear here..." readonly></textarea>

        <div id="attribution" class="attribution"></div>

      </div>

    </div>


    <button id="generateButton" class="translate-btn">Generate</button>

  </div>


  <div id="disclaimerModal" class="disclaimer-modal">

    <div class="disclaimer-content">

      <h2>Disclaimer</h2>

      <p>The text you are about to generate is created by artificial intelligence. It may not be entirely accurate or reliable. Do you wish to proceed?</p>

      <div class="disclaimer-buttons">

        <button id="acceptDisclaimer" class="disclaimer-btn accept-btn">Yes</button>

        <button id="rejectDisclaimer" class="disclaimer-btn reject-btn">No</button>

      </div>

    </div>

  </div>


  <script>

    class TextGenerator {

      constructor() {

        this.inputText = document.getElementById('inputText');

        this.outputText = document.getElementById('outputText');

        this.generateButton = document.getElementById('generateButton');

        this.modelSelect = document.getElementById('modelSelect');

        this.disclaimerModal = document.getElementById('disclaimerModal');

        this.attribution = document.getElementById('attribution');

        

        this.init();

      }

    

      init() {

        // 綁定按鈕事件

        this.generateButton.addEventListener('click', () => this.showDisclaimer());

        

        // Disclaimer 確認按鈕

        document.getElementById('acceptDisclaimer').addEventListener('click', () => {

          this.disclaimerModal.style.display = 'none';

          this.generate();

        });

        

        // Disclaimer 拒絕按鈕

        document.getElementById('rejectDisclaimer').addEventListener('click', () => {

          this.disclaimerModal.style.display = 'none';

          this.outputText.value = 'You must accept the disclaimer to generate text.';

        });

      }

    

      // 顯示 Disclaimer 視窗

      showDisclaimer() {

        this.disclaimerModal.style.display = 'flex';

      }

    

      // 與後端溝通並顯示結果

      async generate() {

        // 如果沒有輸入任何文字,就不執行

        if (!this.inputText.value.trim()) {

          return;

        }

    

        this.generateButton.disabled = true;

        this.generateButton.textContent = 'Generating...';

    

        try {

          const response = await fetch('/api/ai_completion', {

            method: 'POST',

            headers: {

              'Content-Type': 'application/json',

              'Accept': 'application/json',

            },

            // 將使用者輸入與選擇的模型傳給後端

            body: JSON.stringify({

              model: this.modelSelect.value,

              prompt: this.inputText.value

            }),

          });

    

          // 解析後端回傳的 JSON

          const data = await response.json();

          console.log('Response data:', data); // 檢查後端實際回傳的內容


          // 確認是否有 generatedText

          if (data && data.generatedText) {

            this.outputText.value = data.generatedText;

            this.attribution.style.display = 'block';

            this.attribution.textContent = `This text was generated by ${this.modelSelect.value}`;

          } else {

            this.outputText.value = 'No "generatedText" found in the response.';

          }

        } catch (error) {

          console.error('Generation error:', error);

          this.outputText.value = 'An error occurred during text generation. Please try again.';

        } finally {

          this.generateButton.disabled = false;

          this.generateButton.textContent = 'Generate';

        }

      }

    }

    

    // 初始化 TextGenerator

    new TextGenerator();

  </script>

</body>

</html>




從這張截圖顯示的資訊來看,關鍵問題在於以下兩點:

  1. Failed to load resource: the server responded with a status of 404
    這表示前端呼叫的 API 路徑(/api/ai_completion)並不存在或是找不到。換句話說,瀏覽器試圖從 cdpn.io/api/ai_completion 拿取資料,但伺服器回傳了「404 Not Found」,表示這個路由或資源不在該位置。

  2. 回傳資料為空物件
    當 API 回傳 404 時,前端可能還是會嘗試去 response.json(),但最後只拿到一個空的 {} 或者根本沒有正確的 JSON 格式,因此最終呈現 undefined 或空物件。


為何會發生 404?

  1. API 路徑錯誤

    • 你在前端程式中使用 fetch('/api/ai_completion', ...),但實際上 CodePen 或你的伺服器並沒有這個路由。
    • 如果你只是在 CodePen 上測試前端程式,而沒有在 CodePen 上建立對應的後端路由,就會出現 404。
  2. 本地後端沒有對外公開

    • 如果你的後端只在本地執行,卻用 CodePen 執行前端,CodePen 並不知道如何呼叫你本地的 API;除非你有額外做 Proxy 或部署後端到雲端。
  3. 部署位置或跨網域問題

    • 如果你有自己的後端伺服器,路徑可能是 https://yourdomain.com/api/ai_completion,但在 CodePen 裡卻直接寫 /api/ai_completion,導致它嘗試從 CodePen 伺服器的相對路徑呼叫,當然會找不到。

建議解決方法

  1. 確認後端是否存在並可正常被呼叫

    • 先使用 Postman、cURL 或瀏覽器直接打開後端 API 網址,確定可以正確取得資料。
    • 如果後端已經部署到某個雲端服務(例如 Vercel、Heroku、Render 等),確認部署後的網址是什麼。
  2. 在前端中使用正確的 API URL

    • 如果後端網址是 https://example.com/api/ai_completion,那麼前端就應該要 fetch('https://example.com/api/ai_completion', ...)
    • 如果你只是本地端測試,可能會是 http://localhost:3000/api/ai_completion 之類的路徑。
  3. 確認跨域設定(CORS)

    • 如果前端與後端不在同一個網域,後端需要正確設定 CORS,否則瀏覽器可能會擋下請求。
  4. 若只想在 CodePen 測試前端 UI

    • 你可以在 CodePen 用一個 Mock API(例如 JSONPlaceholder)來測試前端是否能正確處理回傳格式。
    • 一旦確認前端邏輯正常,再把 API 路徑改成真正的後端位置。

總結

造成這個錯誤的原因是「API 路徑或伺服器設定有誤」,導致呼叫到 404 Not Found。只要修正前端呼叫的 API URL,並確保後端真的能在該 URL 提供服務,就能解決此問題。



嵌入 WebSim 內容

See the Pen websim 到 codepen 為何失敗原因分析 (API 路徑錯誤) by 01 Scratchinai (@01-Scratchinai) on CodePen.