const STORAGE_KEY = "aiclient_custom_subtitle_templates"; let systemTemplatesCache = null; /** * @typedef {Object} SubtitleTemplateItem * @property {string} id * @property {string} name * @property {string} [description] * @property {Record} config * @property {boolean} [isSystem] * @property {number} [is_system] * @property {number} [createdAt] */ /** * @returns {Promise} */ export async function loadSystemSubtitleTemplates() { if (systemTemplatesCache) return systemTemplatesCache; const res = await fetch("/subtitle-templates/system-templates.json"); if (!res.ok) throw new Error("无法加载系统字幕模板"); const data = await res.json(); systemTemplatesCache = (data.subtitleTemplates || []).map((t) => ({ ...t, isSystem: true, is_system: 1, })); return systemTemplatesCache; } /** * @returns {SubtitleTemplateItem[]} */ export function loadCustomSubtitleTemplates() { try { const raw = localStorage.getItem(STORAGE_KEY); if (!raw) return []; const parsed = JSON.parse(raw); return Array.isArray(parsed) ? parsed : []; } catch { return []; } } /** * @param {SubtitleTemplateItem[]} templates */ export function saveCustomSubtitleTemplates(templates) { localStorage.setItem(STORAGE_KEY, JSON.stringify(templates)); } /** * @returns {Promise} */ export async function loadAllSubtitleTemplates() { const system = await loadSystemSubtitleTemplates(); const custom = loadCustomSubtitleTemplates(); return [...system, ...custom]; } /** * @param {string} id * @returns {Promise} */ export async function getSubtitleTemplateById(id) { const all = await loadAllSubtitleTemplates(); return all.find((t) => t.id === id) || null; } /** * @param {string} id */ export function isSystemSubtitleTemplateId(id) { return ( String(id || "").startsWith("template_system") || String(id || "").startsWith("system-") ); } /** * @param {SubtitleTemplateItem} template */ export function cloneSubtitleTemplate(template) { const now = Date.now(); return { ...JSON.parse(JSON.stringify(template)), id: `custom_${now}`, name: `${template.name} 副本`, isSystem: false, is_system: 0, createdAt: now, }; } /** * @param {SubtitleTemplateItem[]} templates */ export function downloadSubtitleTemplatesJson(templates, filename = "subtitle-templates.json") { const blob = new Blob( [ JSON.stringify( { version: "1.0.0", exportedAt: new Date().toISOString(), subtitleTemplates: templates, }, null, 2, ), ], { type: "application/json" }, ); const url = URL.createObjectURL(blob); const a = document.createElement("a"); a.href = url; a.download = filename; a.click(); URL.revokeObjectURL(url); } /** * @param {File} file * @returns {Promise} */ export async function parseImportedSubtitleTemplates(file) { const text = await file.text(); const data = JSON.parse(text); const list = data.subtitleTemplates || data.templates || data; if (!Array.isArray(list)) throw new Error("无效的模板文件格式"); return list.map((t, i) => ({ ...t, id: t.id || `imported_${Date.now()}_${i}`, isSystem: false, is_system: 0, createdAt: t.createdAt || Date.now(), })); }