Files
hr-position-system/js/api.js
DonaldFang 方士碩 880c23b844 refactor: 建立核心 JavaScript 模組
- 建立 js 目錄
- 分離核心模組:
  * config.js - API 端點、常數、資料對應表
  * utils.js - XSS 防護、表單工具、Toast、錯誤處理
  * api.js - LLM API、Position API、CSV API

 使用 ES6 Modules 架構

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-12-05 14:17:41 +08:00

271 lines
7.1 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/**
* API - API 呼叫函式
* 包含所有與後端 API 的通訊邏輯
*/
import { API_BASE_URL } from './config.js';
import { showCopyableError } from './utils.js';
// ==================== LLM API ====================
/**
* 獲取當前選擇的 Ollama 模型
* @returns {string} - 模型名稱
*/
function getOllamaModel() {
return localStorage.getItem('selectedOllamaModel') || 'deepseek-r1:8b';
}
/**
* 調用 Claude/Ollama LLM API
* @param {string} prompt - Prompt 內容
* @param {string} api - API 類型('ollama' 或其他)
* @returns {Promise<Object>} - 解析後的 JSON 回應
*/
export async function callClaudeAPI(prompt, api = 'ollama') {
try {
// 準備請求資料
const requestData = {
api: api,
prompt: prompt,
max_tokens: 2000
};
// 如果使用 Ollama API加入選擇的模型
if (api === 'ollama') {
requestData.model = getOllamaModel();
}
// 調用後端 Flask API避免 CORS 錯誤
const response = await fetch(`${API_BASE_URL}/llm/generate`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(requestData)
});
if (!response.ok) {
const errorData = await response.json();
throw new Error(errorData.error || `API 請求失敗: ${response.status}`);
}
const data = await response.json();
if (!data.success) {
throw new Error(data.error || 'API 調用失敗');
}
// 清理 JSON 代碼塊標記
let responseText = data.text;
responseText = responseText.replace(/```json\n?/g, '').replace(/```\n?/g, '').trim();
return JSON.parse(responseText);
} catch (error) {
console.error('Error calling LLM API:', error);
// 嘗試解析更詳細的錯誤訊息
let errorDetails = error.message;
try {
const errorJson = JSON.parse(error.message);
errorDetails = JSON.stringify(errorJson, null, 2);
} catch (e) {
// 不是 JSON使用原始訊息
}
// 創建可複製的錯誤對話框
showCopyableError({
title: 'AI 生成錯誤',
message: error.message,
details: errorDetails,
suggestions: [
'Flask 後端已啟動 (python start_server.py)',
'已在 .env 文件中配置有效的 LLM API Key',
'網路連線正常',
'嘗試使用不同的 LLM API (DeepSeek 或 OpenAI)'
]
});
throw error;
}
}
// ==================== Position API ====================
/**
* 保存崗位至崗位清單
* @param {Object} positionData - 崗位資料
* @returns {Promise<Object>} - API 回應
*/
export async function savePositionToList(positionData) {
const response = await fetch(`${API_BASE_URL}/positions`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(positionData)
});
if (!response.ok) {
const errorData = await response.json();
throw new Error(errorData.error || '保存失敗');
}
return await response.json();
}
/**
* 載入崗位清單
* @returns {Promise<Array>} - 崗位清單
*/
export async function loadPositionList() {
const response = await fetch(`${API_BASE_URL}/position-list`);
if (!response.ok) {
throw new Error('載入崗位清單失敗');
}
const data = await response.json();
return data.data || [];
}
/**
* 獲取單一崗位描述
* @param {string} positionCode - 崗位編號
* @returns {Promise<Object>} - 崗位描述資料
*/
export async function getPositionDescription(positionCode) {
const response = await fetch(`${API_BASE_URL}/position-descriptions/${positionCode}`);
if (!response.ok) {
if (response.status === 404) {
return null; // 未找到描述
}
throw new Error('載入崗位描述失敗');
}
const data = await response.json();
return data.data;
}
/**
* 保存崗位描述
* @param {Object} descData - 崗位描述資料
* @returns {Promise<Object>} - API 回應
*/
export async function savePositionDescription(descData) {
const response = await fetch(`${API_BASE_URL}/position-descriptions`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(descData)
});
if (!response.ok) {
const errorData = await response.json();
throw new Error(errorData.error || '保存崗位描述失敗');
}
return await response.json();
}
// ==================== CSV API ====================
/**
* 下載崗位 CSV 範本
*/
export async function downloadPositionCSVTemplate() {
window.location.href = `${API_BASE_URL}/positions/csv-template`;
}
/**
* 下載職務 CSV 範本
*/
export async function downloadJobCSVTemplate() {
window.location.href = `${API_BASE_URL}/jobs/csv-template`;
}
/**
* 匯入崗位 CSV
* @param {File} file - CSV 檔案
* @returns {Promise<Object>} - API 回應
*/
export async function importPositionsCSV(file) {
const formData = new FormData();
formData.append('file', file);
const response = await fetch(`${API_BASE_URL}/positions/import-csv`, {
method: 'POST',
body: formData
});
if (!response.ok) {
const errorData = await response.json();
throw new Error(errorData.error || 'CSV 匯入失敗');
}
return await response.json();
}
/**
* 匯入職務 CSV
* @param {File} file - CSV 檔案
* @returns {Promise<Object>} - API 回應
*/
export async function importJobsCSV(file) {
const formData = new FormData();
formData.append('file', file);
const response = await fetch(`${API_BASE_URL}/jobs/import-csv`, {
method: 'POST',
body: formData
});
if (!response.ok) {
const errorData = await response.json();
throw new Error(errorData.error || 'CSV 匯入失敗');
}
return await response.json();
}
/**
* 匯出完整崗位資料
*/
export async function exportCompletePositionData() {
window.location.href = `${API_BASE_URL}/position-list/export`;
}
// ==================== Ollama Connection Test ====================
/**
* 測試 Ollama 連線
* @returns {Promise<boolean>} - 連線是否成功
*/
export async function testOllamaConnection() {
try {
const response = await fetch(`${API_BASE_URL}/llm/generate`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
api: 'ollama',
model: getOllamaModel(),
prompt: '請回答:「連線測試成功」',
max_tokens: 50
})
});
if (!response.ok) {
return false;
}
const data = await response.json();
return data.success === true;
} catch (error) {
console.error('Ollama 連線測試失敗:', error);
return false;
}
}