Featured image of post Batch Import and Export of Dify Workflows Using JavaScript

Batch Import and Export of Dify Workflows Using JavaScript

📌 Introduction

在 Dify 上目前還沒有一種比較可靠的版本控制模式,目前我只想到將 Dify 的工作流匯出變成 DSL 格式後保存起來,當需要用到的時候再把匯出的 DSL 檔案匯入回去,但當遇到一次需要匯入/匯出很多的工作流時會比較花時間,所以想要用程式的方式讓這些工作流可以批次的上傳/下載,因此有了這一篇文章的產生,分享給需要用到的人可以參考看看。

這篇文章來分享如何用 JavaScript 寫的程式,達到一次將多個 Dify 的工作流下載下來,或是一次上傳多個工作流。

匯入和匯出的程式碼都是可以直接在瀏覽器上的「指控台」執行,因此可以根據需要的功能貼上下方對應功能的程式碼,就可以達到一次匯入/匯出多個工作流。

📝 Steps

1. 開啟瀏覽器上的「主控台」

要直接開啟瀏覽器的開發者工具中的「主控台」(Console),可以使用以下的快捷鍵:

Safari

  1. 先啟用開發者模式:
    • 打開 Safari,點擊 Safari > 偏好設定 > 高級,勾選「顯示開發者選單」。
  2. 使用快捷鍵:
    • Mac: Cmd + Option + C(這會直接跳到主控台)

Google Chrome 和 Microsoft Edge

  1. Windows/Linux: Ctrl + Shift + J
  2. Mac: Cmd + Option + J

Mozilla Firefox

  1. Windows/Linux: Ctrl + Shift + K
  2. Mac: Cmd + Option + K

2. 根據功能選擇貼上以下程式碼並按 Enter 並執行

匯入的程式碼:

// 建立一個隱藏的檔案選擇器
const fileInput = document.createElement('input');
fileInput.type = 'file';
fileInput.style.display = 'none';
fileInput.accept = '.yml'; // 限制只能上傳 .yml 檔案
fileInput.multiple = true; // 允許多選檔案

// 添加檔案變更事件監聽器
fileInput.addEventListener('change', (event) => {
    const files = event.target.files; // 取得所有選擇的檔案

    if (files.length === 0) {
        console.log('沒有選擇檔案');
        return;
    }

    Array.from(files).forEach((file, index) => {
        const reader = new FileReader(); // 建立 FileReader 來讀取檔案內容

        // 當檔案讀取完成後
        reader.onload = (e) => {
            // 讀取的檔案內容
            const fileContent = e.target.result;

            // 將內容中的實際換行替換為 \n
            const escapedContent = fileContent.replace(/\n/g, '\\n');

            // 將內容包裝為 {"data":"<內容>"}
            const wrappedContent = `{"data":"${escapedContent}"}`;

            console.log(`檔案 ${index + 1} (${file.name}) 包裝後的內容:`);
            console.log(wrappedContent); // 輸出包裝後的檔案內容到控制台

            // 發送 POST 請求到 API
            fetch('http://localhost/console/api/apps/import', {
                method: 'POST',
                headers: {
                    'Content-Type': 'application/json',
                    'Authorization': `Bearer ${localStorage.getItem('console_token')}` // 可選的授權標頭
                },
                body: wrappedContent // 發送包裝後的內容
            })
                .then((response) => {
                    if (!response.ok) {
                        throw new Error(`上傳檔案失敗 - 狀態碼: ${response.status}`);
                    }
                    return response.json(); // 解析伺服器的回應
                })
                .then((result) => {
                    console.log(`檔案 ${index + 1} (${file.name}) 上傳成功:`, result);
                })
                .catch((error) => {
                    console.error(`檔案 ${index + 1} (${file.name}) 上傳失敗:`, error);
                });
        };

        // 當檔案讀取出錯
        reader.onerror = (error) => {
            console.error(`檔案 ${index + 1} (${file.name}) 讀取錯誤:`, error);
        };

        // 開始讀取檔案,使用 text 格式讀取
        reader.readAsText(file);
    });
});

// 自動觸發檔案選擇
fileInput.click();

匯出的程式碼:

const token = localStorage.getItem('console_token'); // 從 Local Storage 取得 Token

// 從目前瀏覽器的網址中動態取得網域和 tagIDs
const origin = window.location.origin; // 取得目前頁面的網域 (如 http://localhost)
const urlParams = new URLSearchParams(window.location.search);
const tagIDs = urlParams.get('tagIDs'); // 取得 ?tagIDs= 後的值

if (!tagIDs) {
    console.error('URL 中沒有找到 tagIDs 參數');
} else {
    // 發送第一個 API 請求取得 myList
    const apiUrl = `${origin}/console/api/apps?page=1&limit=30&name=&tag_ids=${tagIDs}`;

    fetch(apiUrl, {
        method: 'GET',
        headers: {
            'Authorization': `Bearer ${token}`, // 包含 Token
            'Content-Type': 'application/json'
        }
    })
        .then(response => {
            if (!response.ok) {
                throw new Error('Network response was not ok');
            }
            return response.json(); // 自動解析 JSON 為 JavaScript 物件
        })
        .then(data => {
            const myList = data["data"]; // 假設資料結構是 { "data": [...] }
            console.log('成功取得資料:', myList);

            // 遍歷 myList 並發送請求
            myList.forEach(item => {
                const id = item["id"]; // 取得每個項目的 ID
                const name = item["name"]; // 檔案名稱改為 item["name"]
                const exportUrl = `${origin}/console/api/apps/${id}/export?include_secret=false`;

                // 發送請求到 export URL
                fetch(exportUrl, {
                    method: 'GET',
                    headers: {
                        'Authorization': `Bearer ${token}`,
                        'Content-Type': 'application/json'
                    }
                })
                    .then(response => {
                        if (!response.ok) {
                            throw new Error(`Export request failed for ID ${id}: ${response.statusText}`);
                        }
                        return response.json();
                    })
                    .then(exportData => {
                        console.log(`Export 成功 - ID: ${id}`, exportData);

                        // 僅取出 exportData["data"]
                        const dataToSave = exportData["data"];

                        // 將 JSON 物件轉成非雙引號格式的文字,去掉最外層的引號
                        const jsonString = JSON.stringify(dataToSave, null, 2)
                            .replace(/\\n/g, '\n') // 替換 \n 為實際換行符
                            .slice(1, -1); // 去掉最外層的雙引號

                        // 將資料儲存到本地檔案
                        const blob = new Blob([jsonString], { type: 'application/x-yaml' });
                        const fileName = `${name}.yml`; // 使用 .yaml 作為檔案名稱
                        const link = document.createElement('a');
                        link.href = URL.createObjectURL(blob);
                        link.download = fileName;
                        link.click();
                        URL.revokeObjectURL(link.href); // 清理 URL 物件
                    })
                    .catch(error => {
                        console.error(`Export 失敗 - ID: ${id}`, error);
                    });
            });
        })
        .catch(error => {
            console.error('發送 API 請求時發生錯誤:', error);
        });
}