📌 Introduction
在 Dify 上目前還沒有一種比較可靠的版本控制模式,目前我只想到將 Dify 的工作流匯出變成 DSL 格式後保存起來,當需要用到的時候再把匯出的 DSL 檔案匯入回去,但當遇到一次需要匯入/匯出很多的工作流時會比較花時間,所以想要用程式的方式讓這些工作流可以批次的上傳/下載,因此有了這一篇文章的產生,分享給需要用到的人可以參考看看。
這篇文章來分享如何用 JavaScript 寫的程式,達到一次將多個 Dify 的工作流下載下來,或是一次上傳多個工作流。
匯入和匯出的程式碼都是可以直接在瀏覽器上的「指控台」執行,因此可以根據需要的功能貼上下方對應功能的程式碼,就可以達到一次匯入/匯出多個工作流。
📝 Steps
1. 開啟瀏覽器上的「主控台」
要直接開啟瀏覽器的開發者工具中的「主控台」(Console),可以使用以下的快捷鍵:
Safari
- 先啟用開發者模式:
- 打開 Safari,點擊 Safari > 偏好設定 > 高級,勾選「顯示開發者選單」。
- 使用快捷鍵:
- Mac:
Cmd + Option + C
(這會直接跳到主控台)
- Mac:
Google Chrome 和 Microsoft Edge
- Windows/Linux:
Ctrl + Shift + J
- Mac:
Cmd + Option + J
Mozilla Firefox
- Windows/Linux:
Ctrl + Shift + K
- 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);
});
}