使用回呼建立人機迴圈工作流程


本教學課程將說明如何建立翻譯工作流程,等待您輸入內容 (即人為迴圈),並連結 Firestore 資料庫、兩個 Cloud Run 函式、Cloud Translation API,以及使用 Firebase SDK 即時更新的網頁。

您可以使用工作流程支援回呼端點 (或 webhook),等待 HTTP 要求抵達該端點,並在稍後時間恢復執行工作流程。在這種情況下,工作流程會等待您輸入內容,以拒絕或驗證某些文字的翻譯,但也可能會等待外部程序。詳情請參閱「使用回呼等待」。

架構

本教學課程會建立一個網頁應用程式,讓您執行下列操作:

  1. 在翻譯網頁上,輸入要從英文翻譯成法文的文字。按一下 [翻譯]
  2. 網頁會呼叫 Cloud Run 函式,啟動工作流程的執行作業。要翻譯的文字會做為參數傳遞至函式和工作流程。
  3. 文字會儲存在 Cloud Firestore 資料庫中。呼叫 Cloud Translation API。系統會將傳回的翻譯儲存在資料庫中。使用 Firebase Hosting 部署網頁應用程式,並即時更新顯示譯文。
  4. 工作流程中的 create_callback 步驟會建立回呼端點網址,並儲存在 Firestore 資料庫中。網頁現在會同時顯示「驗證」和「拒絕」按鈕。
  5. 工作流程現在處於暫停狀態,等待對回呼端點網址的明確 HTTP POST 要求。
  6. 您可以決定是否要驗證或拒絕翻譯內容。點選按鈕會呼叫第二個 Cloud Run 函式,該函式會依序呼叫工作流程建立的回呼端點,並傳遞核准狀態。工作流程會繼續執行,並在 Firestore 資料庫中儲存 truefalse 的核准狀態。

這張圖表概略說明瞭這個程序:

含回呼的 workflow

目標

  • 部署網頁應用程式。
  • 建立 Firestore 資料庫來儲存翻譯要求。
  • 部署 Cloud Run 函式來執行工作流程。
  • 部署及執行工作流程,以便自動化整個流程。

費用

In this document, you use the following billable components of Google Cloud:

To generate a cost estimate based on your projected usage, use the pricing calculator. New Google Cloud users might be eligible for a free trial.

事前準備

貴機構定義的安全性限制,可能會導致您無法完成下列步驟。如需疑難排解資訊,請參閱「在受限的 Google Cloud 環境中開發應用程式」。

  1. Sign in to your Google Cloud account. If you're new to Google Cloud, create an account to evaluate how our products perform in real-world scenarios. New customers also get $300 in free credits to run, test, and deploy workloads.
  2. Install the Google Cloud CLI.

  3. If you're using an external identity provider (IdP), you must first sign in to the gcloud CLI with your federated identity.

  4. To initialize the gcloud CLI, run the following command:

    gcloud init
  5. Create or select a Google Cloud project.

    • Create a Google Cloud project:

      gcloud projects create PROJECT_ID

      Replace PROJECT_ID with a name for the Google Cloud project you are creating.

    • Select the Google Cloud project that you created:

      gcloud config set project PROJECT_ID

      Replace PROJECT_ID with your Google Cloud project name.

  6. Make sure that billing is enabled for your Google Cloud project.

  7. Enable the App Engine, Cloud Build, Cloud Run functions, Firestore, Translation, and Workflows APIs:

    gcloud services enable appengine.googleapis.com<wbr>&nbsp;cloudbuild.googleapis.com<wbr>&nbsp;cloudfunctions.googleapis.com<wbr>&nbsp;firestore.googleapis.com<wbr>&nbsp;translate.googleapis.com<wbr>&nbsp;workflows.googleapis.com
  8. Install the Google Cloud CLI.

  9. If you're using an external identity provider (IdP), you must first sign in to the gcloud CLI with your federated identity.

  10. To initialize the gcloud CLI, run the following command:

    gcloud init
  11. Create or select a Google Cloud project.

    • Create a Google Cloud project:

      gcloud projects create PROJECT_ID

      Replace PROJECT_ID with a name for the Google Cloud project you are creating.

    • Select the Google Cloud project that you created:

      gcloud config set project PROJECT_ID

      Replace PROJECT_ID with your Google Cloud project name.

  12. Make sure that billing is enabled for your Google Cloud project.

  13. Enable the App Engine, Cloud Build, Cloud Run functions, Firestore, Translation, and Workflows APIs:

    gcloud services enable appengine.googleapis.com<wbr>&nbsp;cloudbuild.googleapis.com<wbr>&nbsp;cloudfunctions.googleapis.com<wbr>&nbsp;firestore.googleapis.com<wbr>&nbsp;translate.googleapis.com<wbr>&nbsp;workflows.googleapis.com
  14. 更新 Google Cloud CLI 元件:
    gcloud components update
  15. 使用自己的帳戶登入:
    gcloud auth login
  16. 設定本教學課程中使用的專案 ID 和預設位置:
    export GOOGLE_CLOUD_PROJECT=PROJECT_ID
    export REGION=REGION
    gcloud config set workflows/location ${REGION}

    更改下列內容:

    • PROJECT_ID:您的 Google Cloud 專案 ID。您可以在 Google Cloud 控制台的「歡迎」頁面中找到專案 ID。
    • REGION:您選擇的支援工作流程位置

部署第一個 Cloud Run 函式

這個 Cloud Run 函式會啟動工作流程的執行作業。要翻譯的文字會做為參數傳遞至函式和工作流程。

  1. 建立名為 callback-translation 的目錄,並建立名為 invokeTranslationWorkflowtranslationCallbackCallpublic 的子目錄:

    mkdir -p ~/callback-translation/{invokeTranslationWorkflow,translationCallbackCall,public}
  2. 切換至 invokeTranslationWorkflow 目錄:

    cd ~/callback-translation/invokeTranslationWorkflow
  3. 建立名為 index.js 的文字檔案,其中包含以下 Node.js 程式碼:

    const cors = require('cors')({origin: true});
    const {ExecutionsClient} = require('@google-cloud/workflows');
    const client = new ExecutionsClient();
    
    exports.invokeTranslationWorkflow = async (req, res) => {
      cors(req, res, async () => {
        const text = req.body.text;
        console.log(`Translation request for "${text}"`);
    
        const PROJECT_ID = process.env.PROJECT_ID;
        const CLOUD_REGION = process.env.CLOUD_REGION;
        const WORKFLOW_NAME = process.env.WORKFLOW_NAME;
    
        const execResponse = await client.createExecution({
          parent: client.workflowPath(PROJECT_ID, CLOUD_REGION, WORKFLOW_NAME),
          execution: {
            argument: JSON.stringify({text})
          }
        });
        console.log(`Translation workflow execution request: ${JSON.stringify(execResponse)}`);
    
        const execName = execResponse[0].name;
        console.log(`Created translation workflow execution: ${execName}`);
    
        res.set('Access-Control-Allow-Origin', '*');
        res.status(200).json({executionId: execName});
      });
    };
  4. 建立名為 package.json 的文字檔案,其中包含下列 npm 中繼資料:

    {
      "name": "launch-translation-workflow",
      "version": "0.0.1",
      "dependencies": {
        "@google-cloud/workflows": "^1.2.5",
        "cors": "^2.8.5"
      }
    }
    
  5. 使用 HTTP 觸發條件部署函式,並允許未經驗證的存取權:

    gcloud functions deploy invokeTranslationWorkflow \
    --region=${REGION} \
    --runtime nodejs14 \
    --entry-point=invokeTranslationWorkflow \
    --set-env-vars PROJECT_ID=${GOOGLE_CLOUD_PROJECT},CLOUD_REGION=${REGION},WORKFLOW_NAME=translation_validation \
    --trigger-http \
    --allow-unauthenticated

    函式部署作業可能需要幾分鐘的時間才能完成。或者,您也可以在 Google Cloud 控制台使用 Cloud Run 函式介面來部署函式。

  6. 函式部署完成後,您可以確認 httpsTrigger.url 屬性:

    gcloud functions describe invokeTranslationWorkflow

    請記下傳回的網址,以便在後續步驟中使用。

部署第二個 Cloud Run 函式

這個 Cloud Run 函式會向工作流程建立的回呼端點發出 HTTP POST 要求,並傳遞核准狀態,反映翻譯是否已通過驗證或遭到拒絕。

  1. 切換至 translationCallbackCall 目錄:

    cd ../translationCallbackCall
  2. 建立名為 index.js 的文字檔案,其中包含以下 Node.js 程式碼:

    const cors = require('cors')({origin: true});
    const fetch = require('node-fetch');
    
    exports.translationCallbackCall = async (req, res) => {
      cors(req, res, async () => {
        res.set('Access-Control-Allow-Origin', '*');
    
        const {url, approved} = req.body;
        console.log("Approved? ", approved);
        console.log("URL = ", url);
        const {GoogleAuth} = require('google-auth-library');
        const auth = new GoogleAuth();
        const token = await auth.getAccessToken();
        console.log("Token", token);
    
        try {
          const resp = await fetch(url, {
              method: 'POST',
              headers: {
                  'accept': 'application/json',
                  'content-type': 'application/json',
                  'authorization': `Bearer ${token}`
              },
              body: JSON.stringify({ approved })
          });
          console.log("Response = ", JSON.stringify(resp));
    
          const result = await resp.json();
          console.log("Outcome = ", JSON.stringify(result));
    
          res.status(200).json({status: 'OK'});
        } catch(e) {
          console.error(e);
    
          res.status(200).json({status: 'error'});
        }
      });
    };
  3. 建立名為 package.json 的文字檔案,其中包含下列 npm 中繼資料:

    {
      "name": "approve-translation-workflow",
      "version": "0.0.1",
      "dependencies": {
        "cors": "^2.8.5",
        "node-fetch": "^2.6.1",
        "google-auth-library": "^7.1.1"
      }
    }
    
  4. 使用 HTTP 觸發條件部署函式,並允許未經驗證的存取權:

    gcloud functions deploy translationCallbackCall \
    --region=${REGION} \
    --runtime nodejs14 \
    --entry-point=translationCallbackCall \
    --trigger-http \
    --allow-unauthenticated

    函式部署作業可能需要幾分鐘的時間才能完成。或者,您也可以在 Google Cloud 控制台使用 Cloud Run 函式介面來部署函式。

  5. 函式部署完成後,您可以確認 httpsTrigger.url 屬性:

    gcloud functions describe translationCallbackCall

    請記下傳回的網址,以便在後續步驟中使用。

部署工作流程

工作流程由一系列步驟組成,這些步驟可使用 Workflows 語法描述,並以 YAML 或 JSON 格式編寫。這是工作流程的定義。建立工作流程後,您可以部署工作流程,讓系統執行。

  1. 切換至 callback-translation 目錄:

    cd ..
  2. 建立檔案名稱為 translation-validation.yaml 的文字檔案,並在其中加入下列內容:

    main:
        params: [translation_request]
        steps:
            - log_request:
                call: sys.log
                args:
                    text: ${translation_request}
            - vars:
                assign:
                    - exec_id: ${sys.get_env("GOOGLE_CLOUD_WORKFLOW_EXECUTION_ID")}
                    - text_to_translate: ${translation_request.text}
                    - database_root: ${"projects/" + sys.get_env("GOOGLE_CLOUD_PROJECT_ID") + "/databases/(default)/documents/translations/"}
            - log_translation_request:
                call: sys.log
                args:
                    text: ${text_to_translate}
    
            - store_translation_request:
                call: googleapis.firestore.v1.projects.databases.documents.patch
                args:
                    name: ${database_root + exec_id}
                    updateMask:
                        fieldPaths: ['text']
                    body:
                        fields:
                            text:
                                stringValue: ${text_to_translate}
                result: store_translation_request_result
    
            - translate:
                call: googleapis.translate.v2.translations.translate
                args:
                    query:
                        q: ${text_to_translate}
                        target: "FR"
                        format: "text"
                        source: "EN"
                result: translation_result
            - assign_translation:
                assign:
                    - translation: ${translation_result.data.translations[0].translatedText} 
            - log_translation_result:
                call: sys.log
                args:
                    text: ${translation}
    
            - store_translated_text:
                call: googleapis.firestore.v1.projects.databases.documents.patch
                args:
                    name: ${database_root + exec_id}
                    updateMask:
                        fieldPaths: ['translation']
                    body:
                        fields:
                            translation:
                                stringValue: ${translation}
                result: store_translation_request_result   
    
            - create_callback:
                call: events.create_callback_endpoint
                args:
                    http_callback_method: "POST"
                result: callback_details
            - log_callback_details:
                call: sys.log
                args:
                    text: ${callback_details}
    
            - store_callback_details:
                call: googleapis.firestore.v1.projects.databases.documents.patch
                args:
                    name: ${database_root + exec_id}
                    updateMask:
                        fieldPaths: ['callback']
                    body:
                        fields:
                            callback:
                                stringValue: ${callback_details.url}
                result: store_callback_details_result
    
            - await_callback:
                call: events.await_callback
                args:
                    callback: ${callback_details}
                    timeout: 3600
                result: callback_request
            - assign_approval:
                assign:
                    - approved: ${callback_request.http_request.body.approved}
    
            - store_approval:
                call: googleapis.firestore.v1.projects.databases.documents.patch
                args:
                    name: ${database_root + exec_id}
                    updateMask:
                        fieldPaths: ['approved']
                    body:
                        fields:
                            approved:
                                booleanValue: ${approved}
                result: store_approval_result
    
            - return_outcome:
                return:
                    text: ${text_to_translate}
                    translation: ${translation}
                    approved: ${approved}
  3. 建立工作流程後,您可以部署工作流程,但執行工作流程:

    gcloud workflows deploy translation_validation --source=translation-validation.yaml

建立網頁應用程式

建立會呼叫 Cloud 函式以啟動工作流程執行作業的網頁應用程式。網頁會即時更新,顯示翻譯要求的結果。

  1. 切換至 public 目錄:

    cd public
  2. 建立檔案名稱為 index.html 的文字檔案,其中包含下列 HTML 標記。(在後續步驟中,您將修改 index.html 檔案並新增 Firebase SDK 指令碼)。

    <!DOCTYPE html>
    <html lang="en">
    
    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width">
    
        <title>Frenglish translation — Feature Workflows callbacks</title>
    
        <link rel="stylesheet"
            href="https://cdn.jsdelivr.net/npm/@shoelace-style/[email protected]/dist/themes/base.css">
        <script type="module"
            src="https://cdn.jsdelivr.net/npm/@shoelace-style/[email protected]/dist/shoelace.js"></script>
        <link rel="stylesheet" href="./style.css">
    </head>
    
    <body>
        <h1>Translate from English to French</h1>
    
        <sl-form class="form-overview">
            <sl-textarea id="text" placeholder="The quick brown fox jumps over the lazy dog."
                label="English text to translate"></sl-textarea>
            <p></p>
            <sl-button id="translateBtn" type="primary">Translate</sl-button>
            <p></p>
            <sl-alert id="translation" type="primary">
                Le rapide renard brun saute au dessus du chien paresseux.
            </sl-alert>
            <p></p>
            <div id="buttonRow" style="display: none;">
                <sl-button id="validateBtn" type="success">Validate</sl-button>
                <sl-button id="rejectBtn" type="danger">Reject</sl-button>
            </div>
            <p></p>
            <sl-alert id="validationAlert" type="success">
                <sl-icon slot="icon" name="check2-circle"></sl-icon>
                <strong>The translation has been validated</strong><br>
                Glad that you liked our translation! We'll save it in our database.
            </sl-alert>
            <sl-alert id="rejectionAlert" type="danger">
                <sl-icon slot="icon" name="exclamation-octagon"></sl-icon>
                <strong>The translation has been rejected</strong><br>
                A pity the translation isn't good! We'll do better next time!
            </sl-alert>
            <p></p>
            <sl-button id="newBtn" style="display: none;" type="primary">New translation</sl-button>
        </sl-form>
    
        <script src="https://www.gstatic.com/firebasejs/8.6.3/firebase-app.js"></script>
        <script src="https://www.gstatic.com/firebasejs/8.6.3/firebase-firestore.js"></script>
    
        <script>
            var firebaseConfig = {
                apiKey: "XXXX",
                authDomain: "XXXX",
                projectId: "XXXX",
                storageBucket: "XXXX",
                messagingSenderId: "XXXX",
                appId: "XXXX",
                measurementId: "XXXX"
            };
            // Initialize Firebase
            firebase.initializeApp(firebaseConfig);
        </script>
        <script src="./script.js" type="module"></script>
    </body>
    
    </html>
    
  3. 建立檔案名稱為 script.js 的文字檔案,其中包含下列 JavaScript 程式碼:

    document.addEventListener("DOMContentLoaded", async function (event) {
        const textArea = document.getElementById("text");
        textArea.focus();
    
        const newBtn = document.getElementById("newBtn");
        newBtn.addEventListener("sl-focus", event => {
            event.target.blur();
            window.location.reload();
        });
    
        const translationAlert = document.getElementById("translation");
        const buttonRow = document.getElementById("buttonRow");
    
        var callbackUrl = "";
    
        const validationAlert = document.getElementById("validationAlert");
        const rejectionAlert = document.getElementById("rejectionAlert");
        const validateBtn = document.getElementById("validateBtn");
        const rejectBtn = document.getElementById("rejectBtn");
    
        const translateBtn = document.getElementById("translateBtn");
        translateBtn.addEventListener("sl-focus", async event => {
            event.target.disabled = true;
            event.target.loading = true;
            textArea.disabled = true;
    
            console.log("Text to translate = ", textArea.value);
    
            const fnUrl = UPDATE_ME;
    
            try {
                console.log("Calling workflow executor function...");
                const resp = await fetch(fnUrl, {
                    method: "POST",
                    headers: {
                        "accept": "application/json",
                        "content-type": "application/json"
                    },
                    body: JSON.stringify({ text: textArea.value })
                });
                const executionResp = await resp.json();
                const executionId = executionResp.executionId.slice(-36);
                console.log("Execution ID = ", executionId);
    
                const db = firebase.firestore();
                const translationDoc = db.collection("translations").doc(executionId);
    
                var translationReceived = false;
                var callbackReceived =  false;
                var approvalReceived = false;
                translationDoc.onSnapshot((doc) => {
                    console.log("Firestore update", doc.data());
                    if (doc.data()) {
                        if ("translation" in doc.data()) {
                            if (!translationReceived) {
                                console.log("Translation = ", doc.data().translation);
                                translationReceived = true;
                                translationAlert.innerText = doc.data().translation;
                                translationAlert.open = true;
                            }
                        }
                        if ("callback" in doc.data()) {
                            if (!callbackReceived) {
                                console.log("Callback URL = ", doc.data().callback);
                                callbackReceived = true;
                                callbackUrl = doc.data().callback;
                                buttonRow.style.display = "block";
                            }
                        }
                        if ("approved" in doc.data()) {
                            if (!approvalReceived) {
                                const approved = doc.data().approved;
                                console.log("Approval received = ", approved);
                                if (approved) {
                                    validationAlert.open = true;
                                    buttonRow.style.display = "none";
                                    newBtn.style.display = "inline-block";   
                                } else {
                                    rejectionAlert.open = true;
                                    buttonRow.style.display = "none";
                                    newBtn.style.display = "inline-block";
                                }
                                approvalReceived = true;
                            }
                        }
                    }
                });
            } catch (e) {
                console.log(e);
            }
            event.target.loading = false;
        });
    
        validateBtn.addEventListener("sl-focus", async event => {
            validateBtn.disabled = true;
            rejectBtn.disabled = true;
            validateBtn.loading = true;
            validateBtn.blur();
    
            // call callback
            await callCallbackUrl(callbackUrl, true);
        });
    
        rejectBtn.addEventListener("sl-focus", async event => {
            rejectBtn.disabled = true;
            validateBtn.disabled = true;
            rejectBtn.loading = true;
            rejectBtn.blur();
    
            // call callback
            await callCallbackUrl(callbackUrl, false);
        });
    
    });
    
    async function callCallbackUrl(url, approved) {
        console.log("Calling callback URL with status = ", approved);
    
        const fnUrl = UPDATE_ME;
        try {
            const resp = await fetch(fnUrl, {
                method: "POST",
                headers: {
                    "accept": "application/json",
                    "content-type": "application/json"
                },
                body: JSON.stringify({ url, approved })
            });
            const result = await resp.json();
            console.log("Callback answer = ", result);
        } catch(e) {
            console.log(e);
        }
    }
  4. 編輯 script.js 檔案,將 UPDATE_ME 預留位置替換成先前記下的 Cloud Run 函式網址。

    1. translateBtn.addEventListener 方法中,將 const fnUrl = UPDATE_ME; 替換為:

      const fnUrl = "https://REGION-PROJECT_ID.cloudfunctions.net/invokeTranslationWorkflow";

    2. callCallbackUrl 函式中,將 const fnUrl = UPDATE_ME; 替換為:

      const fnUrl = "https://REGION-PROJECT_ID.cloudfunctions.net/translationCallbackCall";

  5. 建立檔案名稱為 style.css 的文字檔案,其中包含下列層疊樣式:

    * {
        font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif;
    }
    
    body {
        margin: 20px;
    }
    
    h1, h2, h3, h4 {
        color: #0ea5e9;
    }
    

將 Firebase 新增至您的網頁應用程式

在本教學課程中,HTML 頁面、JavaScript 指令碼和 CSS 樣式表單會使用 Firebase 代管服務部署為靜態資產,但這些資產可代管在任何位置,並在您自己的電腦上本機提供,以利測試。

建立 Firebase 專案

您必須先建立 Firebase 專案,才能將 Firebase 新增至應用程式。

  1. Firebase 控制台中,按一下「Create a project」,然後從下拉式選單中選取現有的 Google Cloud 專案,即可將 Firebase 資源新增至該專案。

  2. 按一下「繼續」,直到看到新增 Firebase 的選項。

  3. 略過為專案設定 Google Analytics。

  4. 按一下「Add Firebase」

Firebase 會自動為 Firebase 專案佈建資源。程序完成後,您會前往 Firebase 主控台的專案總覽頁面。

向 Firebase 註冊應用程式

建立 Firebase 專案後,您可以將網頁應用程式加進專案。

  1. 在專案總覽頁面的中間,按一下「Web」圖示 (</>) 啟動設定工作流程。

  2. 輸入應用程式的暱稱。

    只有您在 Firebase 控制台中能看到這項資訊。

  3. 暫時略過 Firebase 託管設定。

  4. 按一下「註冊應用程式」,然後前往控制台。

啟用 Cloud Firestore

網頁應用程式會使用 Cloud Firestore 接收及儲存資料。您必須啟用 Cloud Firestore。

  1. 在 Firebase 控制台的「Build」專區中,按一下「Firestore 資料庫」

    (您可能需要先展開左側導覽窗格,才能看到「Build」專區)。

  2. 在 Cloud Firestore 窗格中,按一下「建立資料庫」

  3. 選取「以測試模式啟動」,使用以下安全性規則:

    rules_version = '2';
    service cloud.firestore {
    match /databases/{database}/documents {
      match /{document=**} {
        allow read, write;
      }
    }
    }
  4. 詳閱安全性規則免責事項後,請按一下「Next」

  5. 設定 Cloud Firestore 資料的儲存位置。您可以接受預設值,也可以選擇附近的區域。

  6. 按一下「啟用」,佈建 Firestore。

新增 Firebase SDK 並初始化 Firebase

Firebase 為多數 Firebase 產品提供 JavaScript 程式庫。使用 Firebase 代管服務前,您必須先將 Firebase SDK 新增至 Web 應用程式。

  1. 如要在應用程式中初始化 Firebase,您必須提供應用程式的 Firebase 專案設定。
    1. 在 Firebase 主控台中前往「專案設定」
    2. 在「您的應用程式」窗格中選取應用程式。
    3. 如要從 CDN 載入 Firebase SDK 程式庫,請在「SDK 設定和設定」窗格中選取「CDN」
    4. 將程式碼片段複製到 <body> 標記底部的 index.html 檔案,取代 XXXX 預留位置值。
  2. 安裝 Firebase JavaScript SDK。

    1. 如果您尚未建立 package.json 檔案,請從 callback-translation 目錄執行下列指令來建立檔案:

      npm init
    2. 安裝 firebase npm 套件,並執行以下指令將其儲存至 package.json 檔案:

      npm install firebase

初始化及部署專案

如要將本機專案檔案連結至 Firebase 專案,您必須初始化專案。接著,您就可以部署網頁應用程式。

  1. callback-translation 目錄中執行下列指令:

    firebase init
  2. 選取 Configure files for Firebase Hosting and (optionally) set up GitHub Action deploys 選項。

  3. 選擇使用現有專案,然後輸入專案 ID。

  4. 接受 public 做為預設的公開根目錄。

  5. 選擇要設定的單頁應用程式。

  6. 略過設定自動建構及部署作業。

  7. File public/index.html already exists. Overwrite? 提示中輸入「否」

  8. 切換至 public 目錄:

    cd public
  9. public 目錄中執行下列指令,將專案部署至網站:

    firebase deploy --only hosting

在本機測試網頁應用程式

Firebase 託管服務可讓您在本機查看及測試變更,並與模擬的後端專案資源互動。使用 firebase serve 時,應用程式會與模擬的後端互動,用於代管內容和設定,但會與實際的後端互動,用於所有其他專案資源。在本教學課程中,您可以使用 firebase serve,但不建議用於進行更廣泛的測試。

  1. public 目錄中執行下列指令:

    firebase serve
  2. 在傳回的本機網址 (通常為 http://localhost:5000) 中開啟網頁應用程式。

  3. 輸入一些英文文字,然後按一下「Translate」

    系統應顯示法文的翻譯內容。

  4. 您現在可以點選「驗證」或「拒絕」

    您可以在 Firestore 資料庫中驗證內容。應類似於以下內容:

    approved: true
    callback: "https://workflowexecutions.googleapis.com/v1/projects/26811016474/locations/us-central1/workflows/translation_validation/executions/68bfce75-5f62-445f-9cd5-eda23e6fa693/callbacks/72851c97-6bb2-45e3-9816-1e3dcc610662_1a16697f-6d90-478d-9736-33190bbe222b"
    text: "The quick brown fox jumps over the lazy dog."
    translation: "Le renard brun rapide saute par-dessus le chien paresseux."
    

    approved 狀態為 truefalse,取決於您是驗證還是拒絕翻譯。

恭喜!您已建立使用工作流程回呼功能的人機迴圈翻譯工作流程。

清除所用資源

如果您是為了這個教學課程建立新專案,請刪除專案。如果您使用現有的專案,且希望保留該專案而不採用本教學課程中新增的變更,請刪除為教學課程建立的資源

刪除專案

如要避免付費,最簡單的方法就是刪除您為了本教學課程所建立的專案。

如要刪除專案:

  1. In the Google Cloud console, go to the Manage resources page.

    Go to Manage resources

  2. In the project list, select the project that you want to delete, and then click Delete.
  3. In the dialog, type the project ID, and then click Shut down to delete the project.

刪除教學課程資源

  1. 移除在教學課程設定期間新增的 gcloud CLI 預設設定:

    gcloud config unset workflows/location
  2. 刪除在本教學課程中建立的工作流程:

    gcloud workflows delete WORKFLOW_NAME
  3. 刪除您在本教學課程中建立的 Cloud Run 函式:

    gcloud functions delete FUNCTION_NAME

    您也可以從 Google Cloud 控制台刪除 Cloud Run 函式。

後續步驟