feat: 實作三個錦囊 AI 功能
- 新增 AI 錦囊 CSS 樣式到 components.css - 創建 js/ai-bags.js 模組,包含: * 5個模組各3個錦囊的預設 prompt 模板 * executeAIBag() - 執行 AI 生成並填充表單 * editBagPrompt() - 編輯自定義 prompt * LocalStorage 管理自定義 prompt - 更新 index.html: * 替換 5 處 AI 按鈕為三個錦囊(崗位基礎、招聘要求、職務、部門職責、崗位描述) * 新增 Prompt 編輯模態框 - 更新 main.js 引入 ai-bags.js 並初始化 - 新增設計文檔:三個錦囊設計.md - 新增欄位對照文檔:表單欄位清單.md、更新欄位名稱.md、ID重命名對照表.md 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
628
js/ai-bags.js
Normal file
628
js/ai-bags.js
Normal file
@@ -0,0 +1,628 @@
|
||||
/**
|
||||
* AI Bags - 三個錦囊功能
|
||||
* 提供可自定義 prompt 的 AI 生成按鈕
|
||||
*/
|
||||
|
||||
import { callClaudeAPI } from './api.js';
|
||||
import { showToast, fillIfEmpty } from './utils.js';
|
||||
import { getPositionFormData, getJobFormData, getJobDescFormData, getDeptFunctionFormData } from './ui.js';
|
||||
|
||||
// ==================== 預設 Prompt 模板 ====================
|
||||
|
||||
const DEFAULT_PROMPTS = {
|
||||
positionBasic: {
|
||||
bag1: {
|
||||
title: '簡化版',
|
||||
subtitle: '僅必填欄位',
|
||||
prompt: `你是專業人資顧問,熟悉半導體製造業。請生成崗位基礎資料(僅必填欄位)。
|
||||
|
||||
已填寫的資料:{existingData}
|
||||
需要生成的欄位:positionCode, positionName
|
||||
|
||||
請用繁體中文,返回 JSON 格式,不要有任何其他文字。`
|
||||
},
|
||||
bag2: {
|
||||
title: '標準版',
|
||||
subtitle: '常用欄位',
|
||||
prompt: `你是專業人資顧問,熟悉半導體製造業。請生成崗位基礎資料(標準版)。
|
||||
|
||||
已填寫的資料:{existingData}
|
||||
需要生成的欄位:positionCode, positionName, positionCategory, positionLevel, headcount
|
||||
|
||||
欄位說明:
|
||||
- positionCode: 崗位編號(格式如 ENG-001)
|
||||
- positionName: 崗位名稱
|
||||
- positionCategory: 崗位類別代碼(01=技術職, 02=管理職, 03=業務職, 04=行政職)
|
||||
- positionLevel: 崗位級別(L1-L7)
|
||||
- headcount: 編制人數(1-10)
|
||||
|
||||
請用繁體中文,返回 JSON 格式,不要有任何其他文字。`
|
||||
},
|
||||
bag3: {
|
||||
title: '詳細版',
|
||||
subtitle: '所有欄位',
|
||||
prompt: `你是專業人資顧問,熟悉半導體製造業的人資所有流程。請生成完整的崗位基礎資料。
|
||||
|
||||
已填寫的資料:{existingData}
|
||||
需要生成的欄位:positionCode, positionName, positionCategory, positionNature, headcount, positionLevel, positionDesc, positionRemark
|
||||
|
||||
欄位說明:
|
||||
- positionCode: 崗位編號(格式如 ENG-001, MGR-002)
|
||||
- positionName: 崗位名稱
|
||||
- positionCategory: 崗位類別代碼(01=技術職, 02=管理職, 03=業務職, 04=行政職)
|
||||
- positionNature: 崗位性質代碼(FT=全職, PT=兼職, CT=約聘, IN=實習)
|
||||
- headcount: 編制人數(1-10之間的數字字串)
|
||||
- positionLevel: 崗位級別(L1到L7)
|
||||
- positionDesc: 崗位描述(條列式,用換行分隔)
|
||||
- positionRemark: 崗位備注(條列式,用換行分隔)
|
||||
|
||||
請用繁體中文,返回 JSON 格式,不要有任何其他文字。`
|
||||
}
|
||||
},
|
||||
positionRecruit: {
|
||||
bag1: {
|
||||
title: '基本需求',
|
||||
subtitle: '核心要求',
|
||||
prompt: `請生成「{positionName}」的基本招聘要求。
|
||||
|
||||
已填寫的資料:{existingData}
|
||||
需要生成的欄位:minEducation, workExperience, jobType, jobTitle
|
||||
|
||||
請用繁體中文,返回 JSON 格式,不要有任何其他文字。`
|
||||
},
|
||||
bag2: {
|
||||
title: '標準需求',
|
||||
subtitle: '完整資訊',
|
||||
prompt: `請生成「{positionName}」的標準招聘要求。
|
||||
|
||||
已填寫的資料:{existingData}
|
||||
需要生成的欄位:minEducation, salaryRange, workExperience, jobType, recruitPosition, jobTitle, positionReq, skillReq
|
||||
|
||||
欄位說明:
|
||||
- minEducation: 最低學歷代碼(HS=高中職, JC=專科, BA=大學, MA=碩士, PHD=博士)
|
||||
- salaryRange: 薪酬范圍代碼(A=30000以下, B=30000-50000, C=50000-80000, D=80000-120000, E=120000以上, N=面議)
|
||||
- workExperience: 工作經驗年數(0=不限, 1, 3, 5, 10)
|
||||
- jobType: 工作性質代碼(FT=全職, PT=兼職, CT=約聘, DP=派遣)
|
||||
- recruitPosition: 招聘職位代碼(ENG=工程師, MGR=經理, AST=助理, OP=作業員, SAL=業務)
|
||||
- positionReq: 崗位要求(條列式,用換行分隔)
|
||||
- skillReq: 技能要求(條列式,用換行分隔)
|
||||
|
||||
請用繁體中文,返回 JSON 格式,不要有任何其他文字。`
|
||||
},
|
||||
bag3: {
|
||||
title: '完整需求',
|
||||
subtitle: '18個欄位',
|
||||
prompt: `請生成「{positionName}」的完整招聘要求資料。
|
||||
|
||||
已填寫的資料:{existingData}
|
||||
需要生成所有空白欄位。
|
||||
|
||||
欄位說明:
|
||||
- minEducation: 最低學歷代碼(HS=高中職, JC=專科, BA=大學, MA=碩士, PHD=博士)
|
||||
- requiredGender: 性別要求代碼(M=限男性, F=限女性, N=不限)
|
||||
- salaryRange: 薪酬范圍代碼(A=30000以下, B=30000-50000, C=50000-80000, D=80000-120000, E=120000以上, N=面議)
|
||||
- workExperience: 工作經驗年數(0=不限, 1, 3, 5, 10)
|
||||
- minAge: 最低年齡(18-65)
|
||||
- maxAge: 最高年齡(18-65)
|
||||
- jobType: 工作性質代碼(FT=全職, PT=兼職, CT=約聘, DP=派遣)
|
||||
- recruitPosition: 招聘職位代碼(ENG=工程師, MGR=經理, AST=助理, OP=作業員, SAL=業務)
|
||||
- jobTitle: 職位名稱
|
||||
- jobDesc: 工作內容(條列式,用換行分隔)
|
||||
- positionReq: 崗位要求(條列式,用換行分隔)
|
||||
- titleReq: 職稱要求(條列式,用換行分隔)
|
||||
- majorReq: 科系要求(多個科系用逗號分隔)
|
||||
- skillReq: 技能要求(條列式,用換行分隔)
|
||||
- langReq: 語言要求(條列式,用換行分隔)
|
||||
- otherReq: 其他要求(條列式,用換行分隔)
|
||||
- superiorPosition: 直屬主管職位
|
||||
- recruitRemark: 招聘備注
|
||||
|
||||
請用繁體中文,返回 JSON 格式,不要有任何其他文字。`
|
||||
}
|
||||
},
|
||||
jobBasic: {
|
||||
bag1: {
|
||||
title: '簡化版',
|
||||
subtitle: '核心欄位',
|
||||
prompt: `請生成職務基礎資料(簡化版)。
|
||||
|
||||
已填寫的資料:{existingData}
|
||||
需要生成的欄位:jobCode, jobName, jobCategoryCode
|
||||
|
||||
請用繁體中文,返回 JSON 格式,不要有任何其他文字。`
|
||||
},
|
||||
bag2: {
|
||||
title: '標準版',
|
||||
subtitle: '常用欄位',
|
||||
prompt: `請生成職務基礎資料(標準版)。
|
||||
|
||||
已填寫的資料:{existingData}
|
||||
需要生成的欄位:jobCode, jobName, jobNameEn, jobCategoryCode, jobLevel, jobHeadcount
|
||||
|
||||
欄位說明:
|
||||
- jobCode: 職務編號(格式如 J001)
|
||||
- jobName: 職務名稱(繁體中文)
|
||||
- jobNameEn: 職務名稱英文
|
||||
- jobCategoryCode: 職務類別代碼(01=技術類, 02=管理類, 03=業務類, 04=行政類)
|
||||
- jobLevel: 職務級別(J1-J7)
|
||||
- jobHeadcount: 職務人數(1-100)
|
||||
|
||||
請用繁體中文,返回 JSON 格式,不要有任何其他文字。`
|
||||
},
|
||||
bag3: {
|
||||
title: '完整版',
|
||||
subtitle: '所有欄位',
|
||||
prompt: `請生成完整的職務基礎資料。
|
||||
|
||||
已填寫的資料:{existingData}
|
||||
需要生成所有空白欄位。
|
||||
|
||||
欄位說明:
|
||||
- jobCode: 職務編號(格式如 J001)
|
||||
- jobName: 職務名稱(繁體中文)
|
||||
- jobNameEn: 職務名稱英文
|
||||
- jobCategoryCode: 職務類別代碼(01=技術類, 02=管理類, 03=業務類, 04=行政類)
|
||||
- jobLevel: 職務級別(J1-J7)
|
||||
- jobHeadcount: 職務人數(1-100)
|
||||
- jobEffectiveDate: 生效日期(YYYY-MM-DD)
|
||||
- jobSortOrder: 排序順序(1-999)
|
||||
- hasAttendanceBonus: 是否有全勤獎金(true/false)
|
||||
- hasHousingAllowance: 是否有住宿津貼(true/false)
|
||||
- jobRemark: 職務備註
|
||||
|
||||
請用繁體中文,返回 JSON 格式,不要有任何其他文字。`
|
||||
}
|
||||
},
|
||||
deptFunction: {
|
||||
bag1: {
|
||||
title: '基本版',
|
||||
subtitle: '核心資訊',
|
||||
prompt: `請生成部門職責資料(基本版)。
|
||||
|
||||
已填寫的資料:{existingData}
|
||||
需要生成的欄位:deptFunctionCode, deptFunctionName, deptFunctionBU, deptFunctionDept
|
||||
|
||||
請用繁體中文,返回 JSON 格式,不要有任何其他文字。`
|
||||
},
|
||||
bag2: {
|
||||
title: '標準版',
|
||||
subtitle: '含職責描述',
|
||||
prompt: `請生成部門職責資料(標準版)。
|
||||
|
||||
已填寫的資料:{existingData}
|
||||
需要生成的欄位:deptFunctionCode, deptFunctionName, deptFunctionBU, deptFunctionDept, deptManager, deptMission, deptCoreFunctions
|
||||
|
||||
欄位說明:
|
||||
- deptFunctionCode: 部門職責編號(格式如 DF001)
|
||||
- deptFunctionName: 部門職責名稱
|
||||
- deptFunctionBU: 事業單位代碼(BU1-BU5)
|
||||
- deptFunctionDept: 部門代碼(DEPT1-DEPT20)
|
||||
- deptManager: 部門主管
|
||||
- deptMission: 部門使命(簡短描述)
|
||||
- deptCoreFunctions: 核心職能(條列式,用換行分隔)
|
||||
|
||||
請用繁體中文,返回 JSON 格式,不要有任何其他文字。`
|
||||
},
|
||||
bag3: {
|
||||
title: '完整版',
|
||||
subtitle: '含KPI指標',
|
||||
prompt: `請生成完整的部門職責資料。
|
||||
|
||||
已填寫的資料:{existingData}
|
||||
需要生成所有空白欄位。
|
||||
|
||||
欄位說明:
|
||||
- deptFunctionCode: 部門職責編號(格式如 DF001)
|
||||
- deptFunctionName: 部門職責名稱
|
||||
- deptFunctionBU: 事業單位代碼(BU1-BU5)
|
||||
- deptFunctionDept: 部門代碼(DEPT1-DEPT20)
|
||||
- deptManager: 部門主管
|
||||
- deptMission: 部門使命(簡短描述)
|
||||
- deptVision: 部門願景(條列式,用換行分隔)
|
||||
- deptCoreFunctions: 核心職能(條列式,用換行分隔)
|
||||
- deptKPIs: KPI 指標(條列式,用換行分隔)
|
||||
|
||||
請用繁體中文,返回 JSON 格式,不要有任何其他文字。`
|
||||
}
|
||||
},
|
||||
jobDesc: {
|
||||
bag1: {
|
||||
title: '基本版',
|
||||
subtitle: '核心資訊',
|
||||
prompt: `請生成崗位描述資料(基本版)。
|
||||
|
||||
已填寫的資料:{existingData}
|
||||
需要生成的欄位:positionName, department, positionPurpose
|
||||
|
||||
請用繁體中文,返回 JSON 格式,不要有任何其他文字。`
|
||||
},
|
||||
bag2: {
|
||||
title: '標準版',
|
||||
subtitle: '含職責說明',
|
||||
prompt: `請生成崗位描述資料(標準版)。
|
||||
|
||||
已填寫的資料:{existingData}
|
||||
需要生成的欄位:positionName, department, directSupervisor, positionPurpose, mainResponsibilities, education, basicSkills
|
||||
|
||||
欄位說明:
|
||||
- positionName: 崗位名稱
|
||||
- department: 所屬部門
|
||||
- directSupervisor: 直屬主管
|
||||
- positionPurpose: 崗位目的(簡短描述)
|
||||
- mainResponsibilities: 主要職責(條列式,用換行分隔)
|
||||
- education: 學歷要求
|
||||
- basicSkills: 基本技能(條列式,用換行分隔)
|
||||
|
||||
請用繁體中文,返回 JSON 格式,不要有任何其他文字。`
|
||||
},
|
||||
bag3: {
|
||||
title: '完整版',
|
||||
subtitle: '33個欄位',
|
||||
prompt: `請生成完整的崗位描述資料。
|
||||
|
||||
已填寫的資料:{existingData}
|
||||
需要生成所有空白欄位。
|
||||
|
||||
包含以下區塊:
|
||||
1. 基本資訊:empNo, empName, positionCode, versionDate
|
||||
2. 崗位資訊:positionName, department, positionEffectiveDate, directSupervisor, positionGradeJob, reportTo, directReports, workLocation, empAttribute
|
||||
3. 職責與目的:positionPurpose, mainResponsibilities
|
||||
4. 任職要求:education, basicSkills, professionalKnowledge, workExperienceReq, otherRequirements
|
||||
|
||||
請用繁體中文,返回 JSON 格式,不要有任何其他文字。`
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// ==================== LocalStorage 管理 ====================
|
||||
|
||||
/**
|
||||
* 獲取模組的 prompts(優先使用自定義,否則使用預設)
|
||||
* @param {string} module - 模組名稱
|
||||
* @returns {Object} - 包含三個 bag 的 prompt 物件
|
||||
*/
|
||||
function getModulePrompts(module) {
|
||||
try {
|
||||
const saved = localStorage.getItem('aiPrompts');
|
||||
const prompts = saved ? JSON.parse(saved) : {};
|
||||
|
||||
// 如果沒有保存的 prompts,使用預設值
|
||||
if (!prompts[module]) {
|
||||
return DEFAULT_PROMPTS[module] || {};
|
||||
}
|
||||
|
||||
return prompts[module];
|
||||
} catch (e) {
|
||||
console.error('讀取 prompts 失敗:', e);
|
||||
return DEFAULT_PROMPTS[module] || {};
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 保存模組的 prompts
|
||||
* @param {string} module - 模組名稱
|
||||
* @param {number} bagNumber - 錦囊編號 (1-3)
|
||||
* @param {Object} bagData - 包含 title, subtitle, prompt 的物件
|
||||
*/
|
||||
function saveModulePrompt(module, bagNumber, bagData) {
|
||||
try {
|
||||
const saved = localStorage.getItem('aiPrompts');
|
||||
const prompts = saved ? JSON.parse(saved) : {};
|
||||
|
||||
if (!prompts[module]) {
|
||||
prompts[module] = {};
|
||||
}
|
||||
|
||||
prompts[module][`bag${bagNumber}`] = bagData;
|
||||
localStorage.setItem('aiPrompts', JSON.stringify(prompts));
|
||||
|
||||
return true;
|
||||
} catch (e) {
|
||||
console.error('保存 prompt 失敗:', e);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 初始化所有模組的預設 prompts(如果尚未設定)
|
||||
*/
|
||||
function initializeDefaultPrompts() {
|
||||
const saved = localStorage.getItem('aiPrompts');
|
||||
if (!saved) {
|
||||
localStorage.setItem('aiPrompts', JSON.stringify(DEFAULT_PROMPTS));
|
||||
}
|
||||
}
|
||||
|
||||
// ==================== 執行 AI 錦囊 ====================
|
||||
|
||||
/**
|
||||
* 執行 AI 錦囊
|
||||
* @param {string} module - 模組名稱
|
||||
* @param {number} bagNumber - 錦囊編號 (1-3)
|
||||
*/
|
||||
export async function executeAIBag(module, bagNumber) {
|
||||
const bagElement = document.querySelector(`.ai-bag[data-module="${module}"][data-bag="${bagNumber}"]`);
|
||||
if (!bagElement) return;
|
||||
|
||||
// 防止重複點擊
|
||||
if (bagElement.classList.contains('loading')) return;
|
||||
|
||||
try {
|
||||
// 顯示載入狀態
|
||||
bagElement.classList.add('loading');
|
||||
const icon = bagElement.querySelector('.bag-icon');
|
||||
const originalIcon = icon.textContent;
|
||||
icon.innerHTML = '<div class="spinner"></div>';
|
||||
|
||||
// 獲取當前表單資料
|
||||
let existingData = {};
|
||||
if (module === 'positionBasic' || module === 'positionRecruit') {
|
||||
const positionData = getPositionFormData();
|
||||
existingData = module === 'positionBasic' ? positionData.basicInfo : positionData.recruitInfo;
|
||||
} else if (module === 'jobBasic') {
|
||||
existingData = getJobFormData();
|
||||
} else if (module === 'jobDesc') {
|
||||
existingData = getJobDescFormData();
|
||||
} else if (module === 'deptFunction') {
|
||||
existingData = getDeptFunctionFormData();
|
||||
}
|
||||
|
||||
// 獲取 prompt 模板
|
||||
const prompts = getModulePrompts(module);
|
||||
const bagPrompt = prompts[`bag${bagNumber}`];
|
||||
|
||||
if (!bagPrompt || !bagPrompt.prompt) {
|
||||
throw new Error('Prompt 模板不存在');
|
||||
}
|
||||
|
||||
// 替換 prompt 中的變數
|
||||
let finalPrompt = bagPrompt.prompt
|
||||
.replace('{existingData}', JSON.stringify(existingData, null, 2))
|
||||
.replace('{positionName}', existingData.positionName || '此崗位');
|
||||
|
||||
// 調用 AI API
|
||||
const result = await callClaudeAPI(finalPrompt);
|
||||
|
||||
// 填充表單
|
||||
fillFormWithAIResult(module, result);
|
||||
|
||||
showToast('✨ AI 生成成功!');
|
||||
} catch (error) {
|
||||
console.error('AI 錦囊執行失敗:', error);
|
||||
showToast('❌ AI 生成失敗: ' + error.message);
|
||||
} finally {
|
||||
// 恢復正常狀態
|
||||
bagElement.classList.remove('loading');
|
||||
const icon = bagElement.querySelector('.bag-icon');
|
||||
icon.textContent = '🎁';
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 根據 AI 結果填充表單
|
||||
* @param {string} module - 模組名稱
|
||||
* @param {Object} result - AI 返回的 JSON 結果
|
||||
*/
|
||||
function fillFormWithAIResult(module, result) {
|
||||
if (module === 'positionBasic') {
|
||||
// 崗位基礎資料 - 基礎資料頁籤
|
||||
Object.entries(result).forEach(([key, value]) => {
|
||||
fillIfEmpty(key, value);
|
||||
});
|
||||
} else if (module === 'positionRecruit') {
|
||||
// 崗位基礎資料 - 招聘要求頁籤
|
||||
Object.entries(result).forEach(([key, value]) => {
|
||||
fillIfEmpty(key, value);
|
||||
});
|
||||
} else if (module === 'jobBasic') {
|
||||
// 職務基礎資料
|
||||
Object.entries(result).forEach(([key, value]) => {
|
||||
if (key === 'hasAttendanceBonus' || key === 'hasHousingAllowance') {
|
||||
const checkbox = document.getElementById(key);
|
||||
if (checkbox) checkbox.checked = value;
|
||||
} else {
|
||||
fillIfEmpty(key, value);
|
||||
}
|
||||
});
|
||||
} else if (module === 'deptFunction') {
|
||||
// 部門職責
|
||||
Object.entries(result).forEach(([key, value]) => {
|
||||
fillIfEmpty('df_' + key, value);
|
||||
});
|
||||
} else if (module === 'jobDesc') {
|
||||
// 崗位描述
|
||||
if (result.basicInfo) {
|
||||
Object.entries(result.basicInfo).forEach(([key, value]) => {
|
||||
fillIfEmpty('jd_' + key, value);
|
||||
});
|
||||
}
|
||||
if (result.positionInfo) {
|
||||
Object.entries(result.positionInfo).forEach(([key, value]) => {
|
||||
fillIfEmpty('jd_' + key, value);
|
||||
});
|
||||
}
|
||||
if (result.responsibilities) {
|
||||
Object.entries(result.responsibilities).forEach(([key, value]) => {
|
||||
fillIfEmpty('jd_' + key, value);
|
||||
});
|
||||
}
|
||||
if (result.requirements) {
|
||||
Object.entries(result.requirements).forEach(([key, value]) => {
|
||||
fillIfEmpty('jd_' + key, value);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ==================== 編輯 Prompt ====================
|
||||
|
||||
/**
|
||||
* 編輯錦囊 Prompt
|
||||
* @param {Event} event - 點擊事件
|
||||
* @param {string} module - 模組名稱
|
||||
* @param {number} bagNumber - 錦囊編號 (1-3)
|
||||
*/
|
||||
export function editBagPrompt(event, module, bagNumber) {
|
||||
event.stopPropagation(); // 防止觸發父元素的 click 事件
|
||||
|
||||
const prompts = getModulePrompts(module);
|
||||
const bagPrompt = prompts[`bag${bagNumber}`];
|
||||
|
||||
if (!bagPrompt) {
|
||||
showToast('❌ Prompt 不存在');
|
||||
return;
|
||||
}
|
||||
|
||||
// 顯示編輯對話框
|
||||
showPromptEditModal(module, bagNumber, bagPrompt);
|
||||
}
|
||||
|
||||
/**
|
||||
* 顯示 Prompt 編輯對話框
|
||||
* @param {string} module - 模組名稱
|
||||
* @param {number} bagNumber - 錦囊編號 (1-3)
|
||||
* @param {Object} bagData - 包含 title, subtitle, prompt 的物件
|
||||
*/
|
||||
function showPromptEditModal(module, bagNumber, bagData) {
|
||||
const modal = document.getElementById('promptEditModal');
|
||||
if (!modal) {
|
||||
console.error('找不到編輯對話框');
|
||||
return;
|
||||
}
|
||||
|
||||
// 填充對話框內容
|
||||
document.getElementById('promptModalTitle').textContent = `編輯錦囊 ${bagNumber} - ${bagData.title}`;
|
||||
document.getElementById('promptTitle').value = bagData.title || '';
|
||||
document.getElementById('promptSubtitle').value = bagData.subtitle || '';
|
||||
document.getElementById('promptContent').value = bagData.prompt || '';
|
||||
|
||||
// 保存當前編輯的模組和錦囊編號
|
||||
modal.dataset.module = module;
|
||||
modal.dataset.bagNumber = bagNumber;
|
||||
|
||||
// 顯示對話框
|
||||
modal.classList.add('show');
|
||||
}
|
||||
|
||||
/**
|
||||
* 保存編輯的 Prompt
|
||||
*/
|
||||
export function savePromptEdit() {
|
||||
const modal = document.getElementById('promptEditModal');
|
||||
const module = modal.dataset.module;
|
||||
const bagNumber = parseInt(modal.dataset.bagNumber);
|
||||
|
||||
const title = document.getElementById('promptTitle').value.trim();
|
||||
const subtitle = document.getElementById('promptSubtitle').value.trim();
|
||||
const prompt = document.getElementById('promptContent').value.trim();
|
||||
|
||||
if (!title || !prompt) {
|
||||
showToast('⚠️ 標題和 Prompt 內容不能為空');
|
||||
return;
|
||||
}
|
||||
|
||||
// 保存到 LocalStorage
|
||||
const success = saveModulePrompt(module, bagNumber, { title, subtitle, prompt });
|
||||
|
||||
if (success) {
|
||||
// 更新頁面上的錦囊標題
|
||||
const bagElement = document.querySelector(`.ai-bag[data-module="${module}"][data-bag="${bagNumber}"]`);
|
||||
if (bagElement) {
|
||||
const titleElement = bagElement.querySelector('.bag-title');
|
||||
const subtitleElement = bagElement.querySelector('.bag-subtitle');
|
||||
if (titleElement) titleElement.textContent = title;
|
||||
if (subtitleElement) {
|
||||
if (subtitle) {
|
||||
if (!subtitleElement.classList) {
|
||||
const newSubtitle = document.createElement('div');
|
||||
newSubtitle.className = 'bag-subtitle';
|
||||
newSubtitle.textContent = subtitle;
|
||||
bagElement.querySelector('.bag-title').after(newSubtitle);
|
||||
} else {
|
||||
subtitleElement.textContent = subtitle;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
closePromptEditModal();
|
||||
showToast('✅ Prompt 已保存');
|
||||
} else {
|
||||
showToast('❌ 保存失敗');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 關閉編輯對話框
|
||||
*/
|
||||
export function closePromptEditModal() {
|
||||
const modal = document.getElementById('promptEditModal');
|
||||
if (modal) {
|
||||
modal.classList.remove('show');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 重置為預設 Prompt
|
||||
*/
|
||||
export function resetToDefaultPrompt() {
|
||||
const modal = document.getElementById('promptEditModal');
|
||||
const module = modal.dataset.module;
|
||||
const bagNumber = parseInt(modal.dataset.bagNumber);
|
||||
|
||||
const defaultPrompt = DEFAULT_PROMPTS[module]?.[`bag${bagNumber}`];
|
||||
|
||||
if (!defaultPrompt) {
|
||||
showToast('❌ 找不到預設 Prompt');
|
||||
return;
|
||||
}
|
||||
|
||||
if (confirm('確定要重置為預設 Prompt 嗎?')) {
|
||||
document.getElementById('promptTitle').value = defaultPrompt.title || '';
|
||||
document.getElementById('promptSubtitle').value = defaultPrompt.subtitle || '';
|
||||
document.getElementById('promptContent').value = defaultPrompt.prompt || '';
|
||||
|
||||
showToast('✅ 已重置為預設值');
|
||||
}
|
||||
}
|
||||
|
||||
// ==================== 初始化 ====================
|
||||
|
||||
/**
|
||||
* 初始化錦囊標題
|
||||
*/
|
||||
export function initializeBagTitles() {
|
||||
document.querySelectorAll('.ai-bag').forEach(bag => {
|
||||
const module = bag.dataset.module;
|
||||
const bagNumber = parseInt(bag.dataset.bag);
|
||||
|
||||
const prompts = getModulePrompts(module);
|
||||
const bagPrompt = prompts[`bag${bagNumber}`];
|
||||
|
||||
if (bagPrompt) {
|
||||
const titleElement = bag.querySelector('.bag-title');
|
||||
const subtitleElement = bag.querySelector('.bag-subtitle');
|
||||
|
||||
if (titleElement) titleElement.textContent = bagPrompt.title || `錦囊${bagNumber}`;
|
||||
if (subtitleElement && bagPrompt.subtitle) {
|
||||
subtitleElement.textContent = bagPrompt.subtitle;
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// 頁面載入時初始化
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
initializeDefaultPrompts();
|
||||
initializeBagTitles();
|
||||
});
|
||||
|
||||
// ==================== 掛載到 window ====================
|
||||
|
||||
if (typeof window !== 'undefined') {
|
||||
window.executeAIBag = executeAIBag;
|
||||
window.editBagPrompt = editBagPrompt;
|
||||
window.savePromptEdit = savePromptEdit;
|
||||
window.closePromptEditModal = closePromptEditModal;
|
||||
window.resetToDefaultPrompt = resetToDefaultPrompt;
|
||||
}
|
||||
Reference in New Issue
Block a user