實踐指南:EdgeOne與HAI的夢幻聯動

努力的小雨發表於2024-04-08

在當今快速發展的數字時代,安全和速度已成為網路服務的基石。EdgeOne,作為騰訊雲提供的邊緣安全加速平臺,以其全球部署的節點和強大的安全防護功能,為使用者提供了穩定而高效的網路體驗。而HAI(HyperApplicationInventor),騰訊雲推出的高效能應用服務,透過其易用的圖形化介面和豐富的模型庫,使得AI應用開發變得觸手可及。本文將探討EdgeOne與HAI的結合如何為使用者提供一個既安全又高效的AI應用開發環境。

EdgeOne:全球加速與安全防護

image

  1. DDoS防護:EdgeOne使用Anycast架構在全球建立超過25個清洗中心,能快速檢測並清除網路、傳輸和應用層的DDoS攻擊。
  2. WEB攻擊防護:EdgeOne擁有廣泛的攻擊特徵庫,覆蓋主要的安全威脅,能阻止各類Web攻擊並防禦0day漏洞,透過AI提高檢測準確性,減少誤報。
  3. 智慧CC識別:EdgeOne利用多年經驗,透過分析網路流量和多種引數,自動識別並定位攻擊源,有效對抗CC攻擊。
  4. BOT防護:EdgeOne透過協議、IP和會話特徵識別BOT,結合資料分析建立模型,防止惡意爬蟲攻擊,減少對正常爬蟲的誤傷。
  5. 攻擊溯源:EdgeOne可以捕獲並分析異常事件,提取攻擊資訊,提供監控頁面展示攻擊詳情,幫助使用者調整防護策略。
  6. 全天候監測:騰訊安全團隊24/7實時監控,主動發現並應對威脅,快速響應安全事件。

不多說了,剩下的內容都已經在官方文件中有詳細描述。就我們個人而言,EdgeOne具有以下主要優點,並且我也附上了需要的關鍵文件地址:

  • 乾淨流量計費模式:對於安全防護功能攔截的請求不進行計費。這種計費模式的優勢在於,使用者無需擔心因網路攻擊而產生的額外費用,可以更加專注於業務的發展和最佳化。
  • 免費證書:簡化HTTPS部署,官方文件:https://cloud.tencent.com/document/product/1552/90437
  • 個人版套餐升級:CC防護與自定義規則設定,官方文件:https://cloud.tencent.com/document/product/1552/93128
  • 無域名接入:快速啟用四層代理服務,這個得需要企業認證,個人慎選。官方文件:https://cloud.tencent.com/document/product/1552/96051
  • 自助診斷:快速定位問題,透過輸入診斷連結,使用者可以快速獲得診斷報告和解決指引,從而提高問題解決的效率。官方文件:https://cloud.tencent.com/document/product/1552/81229
  • 折扣活動:年度最低價,價效比之選,官方文件:https://cloud.tencent.com/document/product/1552/96920
  • 無域名接入功能,快速接入 EdgeOne 啟用四層代理服務。https://cloud.tencent.com/document/product/1552/96051

HAI:AI應用的快速開發

HAI提供了視覺化互動介面,支援JupyterLab、WebUI等多種算力連線方式,使得使用者即使沒有深厚的程式設計背景也能輕鬆開發AI應用。這種“有手就能開發”的設計理念,極大地降低了AI技術的應用門檻。支援學術加速,透過線路自動擇優,能夠大幅提升主流學術資源平臺的訪問、下載速度。同時,HAI預裝了StableDiffusion、ChatGLM等熱門模型,使用者可以在數分鐘內構建自己的大語言模型、AI作畫等應用環境。

這個話題不需要詳細討論了,我已經整理了開發中需要了解的主要優點和相關文件,以便大家能夠快速入手開發:

  1. 快速部署:建立高效能應用服務例項,選擇合適的地域、算力方案,並設定例項名稱和硬碟空間。官方文件:https://cloud.tencent.com/document/product/1721/101036
  2. 生成API文件:在已部署的Stable Diffusion應用基礎上,透過JupyterLab部署API服務。官方文件:https://cloud.tencent.com/document/product/1721/102198
  3. 模型下載地址:https://civitai.com/
  4. 匯入外部模型:在騰訊雲高效能應用服務HAI上,可以透過三種方式匯入外部模型,但是為了花錢少一點,我推薦你選擇COS儲存。官方文件:https://cloud.tencent.com/document/product/1721/102523#1bb64c7b-0bc8-4a2a-86f0-ad0e71d8f059

聯動開發

現在讓我們來探討一下為何可以採用聯動開發的方式。我們的目標是以最為經濟實惠的價格部署我們的AI繪畫應用。

  1. HAI高效能伺服器是按照小時收費的,所以我們很多情況下會選擇關機,但是一旦重啟伺服器,外網IP就會變動,導致你的訪問路徑也就隨之變換。但是這一步可以在EdgeOne中使用邊緣函式解決。
  2. EdgeOne本身就已經對我們的伺服器做了相應的安全防護。
  3. HAI高效能伺服器,他是一個Ubuntu20.04環境的虛擬機器,也就是說,我們已經有了一個伺服器,只不過這個伺服器被包裝成了繪畫AI應用,所以不要浪費掉。用起來

我們接下來看下我們會用到哪些技術:HTML、Javascript、CSS、Nginx、Python、邊緣函式中的各個API、Linux基本命令。

準備環境

HAI高效能伺服器

關於建立伺服器的步驟,我就不再進行演示了,請直接檢視官方教程,相關地址也已經提供。我們將重點演示以下內容:下載/上傳模型、透過WEB UI演示AI繪畫功能、下載並配置Nginx以監聽80埠、啟動API介面服務。

下載/上傳模型

這一步我當時使用過如何官方預設的JupyterLab頁面上傳,速度是真的很可觀~我直接放棄了。選擇了COS物件儲存。我們先去下載好看的模型。這裡以https://civitai.com/ 為例。

image

模型下載到本地之後,去建立COS物件儲存服務。

如何建立這麼簡單的教程,我就不演示了,我們直接看上傳和下載。這裡建立儲存桶的時候,所屬地域一定要和你HAI繪畫伺服器在一起。否則會產生很多額外的花銷。謹記~

image

進入儲存桶之後,直接點選頁面的上傳檔案即可,請選擇剛才下載的模型檔案。上傳的過程中不要動瀏覽器,一旦重新整理和關閉瀏覽器就會前功盡棄。

image

稍等片刻,然後點選模型檔案的詳情,進入基本資訊頁面。在這裡,請確保將許可權設定為公有讀取,否則HAI伺服器將無法下載到模型檔案。然後,複製臨時連結。

image

我們進入已經建立好的HAI伺服器。

image

image

image

進入終端,使用一下幾個命令,下載模型檔案到模型資料夾。

cd stable-diffusion-webui/models/Stable-diffusion

wget 臨時連結貼上到此處

等待下載完畢。

WEB UI演示AI繪畫

讓我們前往WEB UI頁面,嘗試一下看看是否成功安裝了。

image

image

在WEB UI頁面上,選擇您自己的模型檔案,並使用一段神奇的提示詞。當然,提示詞越好,生成的結果也會越好。如果有任何不清楚的地方,您可以參考您下載的模型附帶的教程。這樣基本上就沒有問題了。接下來,讓我們再來看看如何讓HAI提供站點服務。

Nginx站點配置

依然是同樣的步驟,在JupyterLab中的終端中直接使用以下命令操作:

  1. 下載Nginx:使用命令 apt install nginx 進行安裝。
  2. 啟動Nginx:使用命令 service nginx start 啟動服務。

當您直接使用IP地址在瀏覽器中訪問後,如果顯示以下介面,那就表示安裝成功了。

image

接下來,我們繼續修改Nginx的配置檔案:
使用以下命令編輯配置檔案:

vim /etc/nginx/sites-available/default

然後,在檔案中新增以下配置:

image

# 配置/images路徑的訪問規則
location /images/  {
            add_header 'Access-Control-Allow-Origin' "$http_origin";
            add_header 'Access-Control-Expose-Headers' 'strict-origin-when-cross-origin';
            alias   /root/stable-diffusion-webui/outputs/star/images/;  # 你的靜態頁面
            expires 1d;
            }
location /sdapi/  {
      # Add CORS headers
     add_header 'Access-Control-Allow-Origin' '*' always;
     add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS' always;
     add_header 'Access-Control-Allow-Headers' 'DNT,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Range' always;
    add_header 'Access-Control-Expose-Headers' 'Content-Length,Content-Range' always;
    proxy_pass  http://127.0.0.1:7862/sdapi/;
    if ($request_method = "OPTIONS"){
            return 200;
        }
    }

這裡配置了/images路徑的訪問規則以及API服務的代理。為什麼要配置代理呢?這顯然是因為繪畫的API服務不支援跨域訪問,所以我們新增了一個反向代理。由於我們的請求是POST非簡單請求,因此瀏覽器還會傳送一個OPTIONS的預檢請求。我也做了相應的處理。以免跨域報錯。

修改完成後,使用命令 service nginx reload 來重新載入Nginx的配置。您可以看到我們配置的/images路徑對應的檔案。這個路徑您可以隨心配置,只要確保其中包含您自己的圖片即可。您可以選擇自己維護這些圖片,也可以透過程式生成。為了演示,我只簡單複製了幾張我使用AI繪畫出來的圖片。

image

讓我們透過Nginx來訪問一下圖片,以確保可以正常訪問。

在瀏覽器中輸入以下地址進行訪問:http://你的IP/images/1.png

image

接下來,我們繼續除錯API服務。請啟動HAI繪畫應用的API服務,使用以下命令:

python launch.py --nowebui --xformers --opt-split-attention --listen --port 7862

然後,在瀏覽器中輸入IP地址和埠號,後面加上/docs路徑即可正常訪問路徑了。

image

讓我們透過工具來測試一下,看看能否透過Nginx正常訪問介面。你可以隨意選擇一個介面路徑進行測試。我選擇了一個簡單的無請求引數的GET請求作為示例。通常情況下,這個請求會帶有埠號。

image

使用除錯工具來呼叫一下Nginx配置的路徑,確保一切正常。

image

如果您還不確定的話,可以透過檢視預設的Nginx訪問路徑來確認。您可以使用以下命令來實時檢視日誌:

tail -f /var/log/nginx/access.log

這樣您就可以確定API是否能夠被正確命中。

image

啟動API介面服務

為了解決Nginx每次關機後無法正常啟動的難題,我們採取了一項巧妙的解決方案:將啟動命令直接寫入API啟動服務介面中,從而確保服務能夠在每次啟動時正確配置Nginx。具體操作如下:首先,我們需要找到專案中的launch.py檔案。一旦找到了該檔案,我們只需在其中新增幾行命令即可。這些命令將確保Nginx在伺服器啟動時得到正確的配置,並能夠順利啟動。這種方法簡單而高效,能夠有效解決Nginx啟動問題,保證服務的穩定性和可靠性。

image

程式碼如下:

import subprocess

# 此處省略很多程式碼~~
def main():
    # 使用subprocess.run()執行命令
    result = subprocess.run(['service', 'nginx', 'start'], stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True)
    
    # 檢查命令是否執行成功
    if result.returncode == 0:
        print("Nginx 已成功啟動")
    else:
        print("啟動 Nginx 時出錯")
        print("錯誤資訊:", result.stderr)
        
# 此處省略很多程式碼~~

為了驗證我們的解決方案的有效性,讓我們再次重新啟動API服務,觀察是否能夠成功啟動Nginx。

image

透過以上步驟,我們的HAI高效能伺服器已經完成了基本配置。無論進行多少次關機重啟,我們的Nginx都會隨著API介面一起啟動,確保了系統的穩定性和可靠性。現在,讓我們繼續深入探討EdgeOne的配置過程。

EdgeOne邊緣函式

在配置EdgeOne時,首先需要確保完成無域名站點的配置,你可以直接參考官方文件進行操作。接著,我們需要建立邊緣函式來實現相應的快取處理。考慮到你沒有企業版許可權,因此無法使用四層加速,但可以透過邊緣函式來達到類似的效果。關於邊緣函式中程式碼的具體使用問題,我建議你仔細閱讀官方文件,其中包含了詳細的操作指南和示例程式碼,可以幫助你快速上手。你可以點選以下連結檢視官方文件:EdgeOne 官方文件

image

在配置邊緣函式時,你可以選擇Hello World模板作為起點,然後依次點選下一步進行配置。雖然選擇了Hello World模板,但它只是一個起點,我們將在此基礎上進行深入的定製,以實現我們所需要的功能。

image

image

完成所有配置後,系統會為你生成一個預設的域名。接下來,我們需要編寫程式碼來實現功能。通常情況下,我會額外編寫一個test函式,專門用於測試。當然,如果你覺得不需要,也可以忽略。在這次配置中,我們建立了兩個邊緣函式。這些函式已經經過我封裝,透過最小的變動來應對HAI伺服器外網IP的變化,確保了系統的穩定性和可靠性。

查詢圖片處理

一個繪畫應用的關鍵之一在於能夠提供優質的圖片展示功能。畢竟,畫作的視覺效果是吸引使用者的關鍵之一。缺少了圖片展示功能,這個應用就好比是一本沒有插圖的畫冊,缺乏吸引力,難以引起使用者的興趣。

async function fetchJquery(event, request) {
  const cache = caches.default;
  // 快取沒有命中,回源並快取
  let response = await fetch(request);

  // 在響應頭新增 Cahe-Control,設定快取時長 10s
  response.headers.append('Cache-Control', 's-maxage=600');
  event.waitUntil(cache.put(request, response.clone()));

  // 未命中快取,設定響應頭標識
  response.headers.append('x-edgefunctions-cache', 'miss');
  return response;
}

async function handleEvent(event) {
  const urlInfo = new URL(event.request.url);
  var url = new URL(event.request.url);
  var ip = url.searchParams.get('ip');
  // 請求非圖片資源
  if (!/\.(jpe?g|png)$/.test(urlInfo.pathname)) {
    return event.respondWith(new Response('Error thrown 沒命中圖片URL' + urlInfo.pathname));
  }
  // 資源地址,也作為快取鍵
  const request = new Request(ip + urlInfo.pathname);
  // 快取預設例項
  const cache = caches.default;

  try {
    // 獲取關聯的快取內容,快取過,介面底層不主動回源,丟擲 504 錯誤
    let response = await cache.match(request);

    // 快取不存在,重新獲取遠端資源
    if (!response) {
      return fetchJquery(event, request);
    }

    // 命中快取,設定響應頭標識
    response.headers.append('x-edgefunctions-cache', 'hit');

    return response;
  } catch (e) {
    await cache.delete(request);
    // 快取過期或其他異常,重新獲取遠端資源
    return fetchJquery(event, request);
  }
}

addEventListener('fetch', (event) => {
  event.respondWith(handleEvent(event));
});

這段程式碼的主要功能是用於處理網路請求並管理資源的快取策略,確保快速響應使用者請求的同時,減少不必要的網路流量。

  1. fetchJquery 函式:
    • 該函式用於處理當資源未被快取或快取過期時的情況。
    • 使用fetch函式發起網路請求,獲取資源的最新響應。
    • 在響應頭中新增Cache-Control,設定資源在服務端快取中的最長有效時間為600秒。
    • 使用cache.put方法將獲取的響應克隆一份並存入快取中,以便後續請求可以直接從快取中獲取。
    • 設定響應頭x-edgefunctions-cachemiss,表示這是一個未命中快取的請求。
    • 返回原始響應給請求者。
  2. handleEvent 函式:
    • 該函式用於檢查請求的URL是否指向一個圖片資源(透過副檔名判斷)。
    • 如果請求的不是圖片資源,將返回一個錯誤響應。
    • 如果是圖片資源,嘗試從快取中獲取請求的資源。
    • 如果快取中存在資源,則設定響應頭x-edgefunctions-cachehit,表示這是一個命中快取的請求,並返回快取中的資源。
    • 如果快取中不存在資源或發生異常(如快取過期),則刪除快取並重新呼叫fetchJquery函式獲取資源。

在訪問圖片資源時,需要特別注意的是,我採用了路徑後面的 IP 引數進行訪問。這是因為 HAI 每次重啟後外網 IP 都會發生變化,如果每次都要修改每個邊緣函式的話,會相當繁瑣。為了避免這種情況,我選擇透過引數傳遞的方式來獲取圖片資源。這樣一來,獲取圖片資源的邊緣函式就不需要再進行任何修改了。另外,你還需要對匹配觸發規則進行配置,比如我設定的規則是包含 "/images" 的路徑才會觸發相應的操作。

image

在瀏覽器中輸入預設域名,並隨意新增 "/images/*.png" 即可訪問相應圖片,前提是該圖片儲存於我們的伺服器上。現在讓我們來觀察一下效果。值得注意的是,這種方式的命中率並不是很高,所以在剛部署完邊緣函式後,需要多重新整理幾次才能看到效果。

image

靜態網頁

這個邊緣函式專門用於處理使用者互動。

const html = `
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>AI繪圖</title>
    <style>
        body {
            font-family: Arial, sans-serif;
        }
        
        .container {
            display: flex;
            justify-content: center;
        }

        .box {
            margin: 20px;
            border: none; /* 移除框的邊框 */
            padding: 10px;
            background-color: #f5f5f5; /* 設定框的背景顏色 */
            border-radius: 10px; /* 新增圓角 */
            display: flex; /* 使用flex佈局 */
            flex-direction: column; /* 垂直佈局 */
            align-items: center; /* 居中對齊 */
            text-align: center; /* 文字居中對齊 */
        }
        .box:nth-child(2) {
          align-self: start; /* 右邊的box頂部對齊 */
        }

        .box-title {
            margin-bottom: 10px;
            margin-right: 500px;
            border-bottom: 1px solid grey; /* 新增標題底部的分隔線 */
            padding-bottom: 5px; /* 新增一些底部間距以增強視覺效果 */
        }

        #image-container {
            width: 512px;
            height: 512px;
            background-color: #eee;
            margin: auto; /* 在父元素中水平居中 */
        }

        #text-description {
            width: 300px;
            height: 100px;
            margin-bottom: 10px;
            border: 1px solid grey; /* 新增外邊框 */
            padding: 5px;
            resize: none; /* 禁止調整大小 */
            font-size: 14px;
            outline: none; /* 移除輸入框預設的外邊框 */
            border-radius: 0; /* 移除圓角 */
        }
        
        #text-description2 {
            width: 300px;
            height: 100px;
            margin-bottom: 10px;
            border: 1px solid grey; /* 新增外邊框 */
            padding: 5px;
            resize: none; /* 禁止調整大小 */
            font-size: 14px;
            outline: none; /* 移除輸入框預設的外邊框 */
            border-radius: 0; /* 移除圓角 */
        }
        
        #dropdown {
            padding: 10px 20px; /* 增大按鈕的內邊距 */
            cursor: pointer;
            margin-bottom: 10px;
            border: 1px solid grey; /* 新增外邊框 */
            width: 100%; /* 將寬度設定為100%以與容器對齊 */
            align-self: flex-start; /* 按鈕左對齊 */
        }
        
        #dropdown2 {
            padding: 10px 20px; /* 增大按鈕的內邊距 */
            cursor: pointer;
            margin-bottom: 10px;
            border: 1px solid grey; /* 新增外邊框 */
            width: 100%; /* 將寬度設定為100%以與容器對齊 */
            align-self: flex-start; /* 按鈕左對齊 */
        }
        
        #acckey {
            padding: 10px 20px; /* 增大按鈕的內邊距 */
            cursor: pointer;
            margin-bottom: 10px;
            border: 1px solid grey; /* 新增外邊框 */
            width: 100%; /* 將寬度設定為100%以與容器對齊 */
            align-self: stretch; /* 按鈕左對齊 */
        }

        #input-container {
            flex-grow: 1;
            display: flex;
            flex-direction: column; /* 垂直佈局 */
            align-items: center; /* 居中對齊 */
        }

        #text-input {
            width: 300px;
            height: 30px;
            margin-bottom: 10px;
        }

        #generate-button {
            padding: 10px 20px; /* 增大按鈕的內邊距 */
            background-color: #4caf50;
            color: white;
            border: none;
            cursor: pointer;
            width: 100%; /* 將寬度設定為100%以與容器對齊 */
            align-self: flex-start; /* 按鈕左對齊 */
        }
        
        @media (max-width: 600px) {
            .container {
                flex-wrap: wrap; /* 在螢幕寬度不足時換行顯示 */
            }

            .box {
                width: 100%; /* 讓框佔滿一行 */
                margin-bottom: 20px; /* 新增底邊距 */
            }
        }
        .image-list {
            display: flex; /* 使用flex佈局 */
            flex-wrap: wrap; /* 允許列表換行 */
            justify-content: space-around; /* 間隔均勻分佈 */
            align-items: center; /* 垂直居中對齊 */
        }

        .image-list img {
            max-width: 18%; /* 圖片最大寬度佔比,根據需要調整 */
            margin: 5px; /* 圖片之間的間隔 */
            object-fit: contain; /* 確保圖片等比例縮放 */
        }
    </style>
</head>
<body>
    <div class="container">
        <div class="box">
            <h2 class="box-title">繪圖結果</h2>
            <div id="image-container">
            </div>
        </div>
        <div class="box">
            <h2 class="box-title">文字描述</h2>
            <div id="input-container">
                <textarea id="text-description" title="文字描述" placeholder="文字描述。
演算法將根據輸入的文字智慧生成與之相關的影像。建議詳細描述畫面主體、細節、場景等,文字描述越豐富,生成效果越精美。
不能為空,推薦使用中文。最多傳512個字元。"></textarea>
                <textarea id="text-description2" title="反向文字描述" placeholder="反向文字描述。
用於一定程度上從反面引導模型生成的走向,減少生成結果中出現描述內容的可能,但不能完全杜絕。
推薦使用中文。最多傳512個字元。"></textarea>
                <select id="dropdown" title="解析度">
                  <option value="768:1024">解析度(預設768:1024)</option>
                  <option value="768:768">768:768</option>
                  <option value="1024:768">1024:768</option>
                </select>
                <select id="dropdown2" title="繪畫風格">
                  <option value="201">繪畫風格(預設日系動漫風格)</option>
                </select>
                <div style="display: flex; justify-content: flex-start; width: 100%;">
                  <input id="acckey" type="password" placeholder="訪問金鑰" style="width: 100%;">
               </div>
                <button id="generate-button">生成</button>
            </div>
        </div>
        
    </div>
    <!-- 圖片列表容器 -->
    <div class="image-list" id="image-list">
        <!-- 圖片將被動態插入到這裡 -->
    </div>
    <script>
    // 頁面載入完成後執行的函式
        window.onload = function() {
            // 假設您已經有了一個函式來發起請求和處理響應
            // 這裡我們直接呼叫這個函式
            createImageList();
        };
    var ip = "http://101.43.51.133";
  document.getElementById("generate-button").addEventListener("click", function() {
    var Ai_Image_Prompt = document.getElementById("text-description").value;
    var Ai_Image_NegativePrompt = document.getElementById("text-description2").value;
    var Ai_Image_Size = document.getElementById("dropdown").value;
    var Ai_Image_Styles = document.getElementById("dropdown2").value;
    var Ai_Image_AccKey = document.getElementById("acckey").value;


    var data = {
      "denoising_strength": 0,
      "override_settings": {
        "sd_model_checkpoint" :"animagineXLV31_v30.safetensors [1449e5b0b9]"
     }, 
      "prompt": Ai_Image_Prompt,
      "negative_prompt": Ai_Image_NegativePrompt,
      "seed": 0,
      "batch_size": 1,
      "n_iter": 1,
      "num_inference_steps": 28,
      "guidance_scale": 7,
      "sdxl_style": "(None)",
      "quality_tags": "(None)",
      "width": 512,
      "height": 512,
      "sampler": "Euler a"
    };

    var xhr = new XMLHttpRequest();
    xhr.open("POST", ip + "/sdapi/v1/txt2img", true);
    xhr.setRequestHeader("Content-Type", "application/json");
    xhr.onreadystatechange = function() {
        if(xhr.readyState === 4){
      if (xhr.status === 200) {
        var response = JSON.parse(xhr.responseText);
          var imageContainer = document.getElementById("image-container"); 
          // 建立一個新的img元素
          var imgElement = document.createElement("img");
          imageContainer.style.backgroundSize = "contain";
            // 設定img元素的src屬性為base64編碼的圖片資料
            imgElement.src = 'data:image/png;base64,'+response.images[0];
            // 設定img元素的alt屬性
            imgElement.alt = "動態生成的圖片";

            // 將img元素新增到imageContainer中
            imageContainer.appendChild(imgElement);
            // 由於img元素是非同步載入的,我們需要等待圖片載入完成後再進行操作
      }else{
          alert("請求錯誤:" + xhr.status);
      }
    }
    };
    xhr.onerror = function() {
        // 處理網路錯誤
        alert("網路錯誤");
    };
    xhr.send(JSON.stringify(data));
  });
  // 處理圖片列表的函式
    function createImageList() {
        var imageList = document.getElementById("image-list");
        imageList.innerHTML = ""; // 清空當前列表
        // 使用for迴圈生成圖片元素
        for (var i = 1; i <= 5; i++) {
            // 建立新的img元素
            var img = document.createElement("img");
            // 設定圖片的src屬性為對應的圖片檔案路徑
            // 假設所有圖片都存放在同一個目錄下
            img.src = "http://ai-zone-2vjn2f1z4giq-1302107156.eo-edgefunctions.com/images/" + i + ".png?ip="+ip;
            img.alt = "Image " + i; // 設定alt屬性
            img.style.width = '100%'; // 設定圖片寬度為100%

            // 將img元素新增到圖片列表容器中
            imageList.appendChild(img);
        }
    }
</script>

</body>
</html>

`;
async function handleRequest(request) {
return new Response(html, {
headers: {
'content-type': 'text/html; charset=UTF-8',
'x-edgefunctions-test': 'Welcome to use Edge Functions.',
},
});
}
addEventListener('fetch', event => {
event.respondWith(handleRequest(event.request));
});

這段程式碼定義了一個HTML頁面,用於展示一個AI繪圖應用的使用者介面,幷包含了與之互動的JavaScript程式碼。主要功能和組成部分如下:

  1. HTML結構
    • 頁面包含一個容器container,用於佈局兩個主要部分:繪圖結果展示和文字描述輸入。
    • 繪圖結果部分包含一個標題和一個影像容器image-container,用於展示AI生成的影像。
    • 文字描述部分包含兩個文字區域text-descriptiontext-description2,供使用者輸入正向描述和反向描述,用於指導AI繪圖。
    • 一個按鈕generate-button,用於觸發AI繪圖過程。
  2. JavaScript邏輯
    • 頁面載入完成後,呼叫createImageList函式,動態生成一個圖片列表並展示在頁面上。
    • 監聽generate-button按鈕的點選事件,收集使用者輸入的描述和選擇的引數,構造一個AI繪圖請求,併傳送到伺服器。
    • 使用XMLHttpRequest物件傳送POST請求到指定的伺服器地址,處理響應並動態更新頁面上的影像容器以展示生成的影像。

我們看一下最終效果:

image

我已經將 IP 地址單獨抽離出來,以確保最小化變動量。如果需要重啟,只需更新 IP 地址,然後重新部署即可重新訪問。

image

讓我們再次觀察一下生成圖片的效果。點選生成時,後臺介面的響應也都是正常的。

image

image

質量確實還有提升空間,我仔細檢查了一下,發現問題可能出在模型的限制上。例如,對於寬高比不能是正方形的影像,可以稍微寬一點或高一點。這樣生成的影像會更具觀賞性。但這些問題都是可以透過除錯解決的。總的來說,我們的EdgeOne和HAI的夢幻聯動已經取得了階段性的成果,算是告一段落了。

總結

本篇文章源於他人創意的啟發與深入思考,我投入了大量時間與精力,致力於EdgeOne與HAI之間的協同除錯,克服了眾多技術奇葩問題。透過不懈的努力,成功地將這兩個強大的平臺整合在一起,打造出一個還不錯的參考案例。這次探索也是我個人首次涉足邊緣函式式產品的實踐之旅。我希望透過分享這次的經歷和成果,能夠激發更多人對這些前沿技術的興趣和探索。如果你對我的專案感興趣,或者從中獲得了靈感與幫助,不妨點贊支援,也歡迎關注我,一起交流學習,共同進步。

相關文章