diff --git a/src/landppt/web/static/js/global_master_templates.js b/src/landppt/web/static/js/global_master_templates.js new file mode 100644 index 0000000..db92f66 --- /dev/null +++ b/src/landppt/web/static/js/global_master_templates.js @@ -0,0 +1,928 @@ +// Global Master Templates Management JavaScript + +async function updateTagFilter() { + const tagFilter = document.getElementById('tagFilter'); + + if (!tagFilter) { + console.warn('Tag filter element not found'); + return; + } + + try { + // Get all templates to extract tags (without pagination) + const response = await fetch('/api/global-master-templates/?active_only=true&page_size=1000'); + if (response.ok) { + const data = await response.json(); + const allTemplates = data.templates || []; + + const allTags = new Set(); + allTemplates.forEach(template => { + if (template.tags && Array.isArray(template.tags)) { + template.tags.forEach(tag => { + if (tag && tag.trim()) { + allTags.add(tag.trim()); + } + }); + } + }); + + // 保存当前选中的值 + const currentValue = tagFilter.value; + + // Clear existing options except "所有标签" + tagFilter.innerHTML = ''; + + // Add tag options + const sortedTags = Array.from(allTags).sort(); + sortedTags.forEach(tag => { + const option = document.createElement('option'); + option.value = tag; + option.textContent = tag; + tagFilter.appendChild(option); + }); + + // 恢复之前选中的值(如果该标签仍然存在) + if (currentValue && sortedTags.includes(currentValue)) { + tagFilter.value = currentValue; + } + + console.log(`Loaded ${sortedTags.length} tags for filter`); + } else { + console.warn('Failed to fetch templates for tag filter:', response.status); + } + } catch (error) { + console.error('Failed to load tags for filter:', error); + // 确保至少有默认选项 + tagFilter.innerHTML = ''; + } +} + +// filterTemplates function removed - now using server-side pagination + +function showLoading(show) { + document.getElementById('loadingIndicator').style.display = show ? 'block' : 'none'; + document.getElementById('templatesGrid').style.display = show ? 'none' : 'grid'; +} + +function openTemplateModal(templateId = null) { + editingTemplateId = templateId; + const modal = document.getElementById('templateModal'); + const title = document.getElementById('modalTitle'); + const form = document.getElementById('templateForm'); + + if (templateId) { + title.textContent = '编辑母版'; + loadTemplateForEdit(templateId); + } else { + title.textContent = '新建母版'; + form.reset(); + } + + modal.style.display = 'flex'; +} + +function closeTemplateModal() { + document.getElementById('templateModal').style.display = 'none'; + editingTemplateId = null; +} + +function openAIGenerationModal() { + document.getElementById('aiGenerationModal').style.display = 'flex'; + document.getElementById('aiGenerationForm').reset(); +} + +function closeAIGenerationModal() { + document.getElementById('aiGenerationModal').style.display = 'none'; + + // 重置模态框状态 + document.getElementById('aiFormContainer').style.display = 'block'; + document.getElementById('aiGenerationProgress').style.display = 'none'; + document.getElementById('aiGenerationComplete').style.display = 'none'; + + // 重置表单 + document.getElementById('aiGenerationForm').reset(); +} + +function closePreviewModal() { + document.getElementById('previewModal').style.display = 'none'; +} + +async function loadTemplateForEdit(templateId) { + try { + const response = await fetch(`/api/global-master-templates/${templateId}`); + if (!response.ok) { + throw new Error('Failed to load template'); + } + + const template = await response.json(); + + document.getElementById('templateName').value = template.template_name; + document.getElementById('templateDescription').value = template.description; + document.getElementById('templateTags').value = template.tags.join(', '); + document.getElementById('isDefault').checked = template.is_default; + document.getElementById('htmlTemplate').value = template.html_template; + } catch (error) { + console.error('Error loading template:', error); + alert('加载模板失败: ' + error.message); + } +} + +async function handleTemplateSubmit(event) { + event.preventDefault(); + + const formData = new FormData(event.target); + const templateData = { + template_name: formData.get('template_name'), + description: formData.get('description'), + html_template: formData.get('html_template'), + tags: formData.get('tags').split(',').map(tag => tag.trim()).filter(tag => tag), + is_default: formData.get('is_default') === 'on' + }; + + try { + let response; + if (editingTemplateId) { + // Update existing template + response = await fetch(`/api/global-master-templates/${editingTemplateId}`, { + method: 'PUT', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify(templateData) + }); + } else { + // Create new template + response = await fetch('/api/global-master-templates/', { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify(templateData) + }); + } + + if (!response.ok) { + const error = await response.json(); + throw new Error(error.detail || 'Failed to save template'); + } + + closeTemplateModal(); + loadTemplates(currentPage); + alert(editingTemplateId ? '模板更新成功!' : '模板创建成功!'); + } catch (error) { + console.error('Error saving template:', error); + alert('保存模板失败: ' + error.message); + } +} + +async function handleAIGeneration(event) { + event.preventDefault(); + + const formData = new FormData(event.target); + const requestData = { + prompt: formData.get('prompt'), + template_name: formData.get('template_name'), + description: formData.get('description'), + tags: formData.get('tags').split(',').map(tag => tag.trim()).filter(tag => tag), + generation_mode: formData.get('generation_mode') || 'text_only' + }; + + // 如果有上传的图片,添加图片数据 + if (uploadedImageData && requestData.generation_mode !== 'text_only') { + requestData.reference_image = { + filename: uploadedImageData.filename, + data: uploadedImageData.data, + size: uploadedImageData.size, + type: uploadedImageData.type + }; + } + + try { + // 切换到进度显示界面 + showAIGenerationProgress(); + + // 开始生成 + await startGeneration(requestData); + + } catch (error) { + console.error('Error generating template:', error); + showAIGenerationError(error.message); + } +} + +function showAIGenerationProgress() { + // 隐藏表单,显示进度 + document.getElementById('aiFormContainer').style.display = 'none'; + document.getElementById('aiGenerationProgress').style.display = 'block'; + document.getElementById('aiGenerationComplete').style.display = 'none'; + + // 重置进度状态 + document.getElementById('statusText').textContent = '正在分析需求...'; +} + +function showAIGenerationComplete() { + // 隐藏进度,显示完成 + document.getElementById('aiGenerationProgress').style.display = 'none'; + document.getElementById('aiGenerationComplete').style.display = 'block'; +} + +function showAIGenerationError(errorMessage) { + console.error('AI generation error:', errorMessage); + + // 显示错误并返回表单 + document.getElementById('aiGenerationProgress').style.display = 'none'; + document.getElementById('aiGenerationComplete').style.display = 'none'; + document.getElementById('aiFormContainer').style.display = 'block'; + + // 重置状态 + document.getElementById('statusText').textContent = '正在分析需求...'; + + alert('AI生成模板失败: ' + errorMessage); +} + +async function startGeneration(requestData) { + try { + // 更新状态 + updateGenerationStatus('正在连接AI服务...'); + + const response = await fetch('/api/global-master-templates/generate', { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify(requestData) + }); + + if (!response.ok) { + const error = await response.json(); + throw new Error(error.detail || 'Failed to generate template'); + } + + // 更新状态 + updateGenerationStatus('AI正在分析需求并生成模板...'); + + // 处理响应 + const result = await response.json(); + console.log('Generation result:', result); // 调试信息 + + // 检查响应格式:可能是包装格式 {success: true, data: {...}} 或直接的模板对象 + let templateData = null; + + if (result.success && result.data) { + // 包装格式 + templateData = result.data; + } else if (result.id && result.template_name) { + // 直接的模板对象格式(已保存的模板) + templateData = result; + } else if (result.html_template) { + // 生成的模板数据格式 + templateData = result; + } + + if (templateData) { + // 更新状态 + updateGenerationStatus('正在处理生成结果...'); + + // 处理生成完成的数据 + await handleGenerationComplete(templateData); + } else { + console.error('Generation failed - unrecognized response format:', result); // 调试信息 + throw new Error(result.message || result.detail || 'Generation failed'); + } + + // 生成完成 + updateGenerationStatus('模板生成完成!'); + showAIGenerationComplete(); + + } catch (error) { + console.error('Generation error:', error); + throw error; + } +} + + + +function showTemplatePreview(htmlTemplate) { + const iframe = document.getElementById('generatedTemplateIframe'); + if (iframe) { + // 创建一个blob URL来显示HTML内容 + const blob = new Blob([htmlTemplate], { type: 'text/html' }); + const url = URL.createObjectURL(blob); + iframe.src = url; + + // 清理之前的URL + iframe.onload = () => { + setTimeout(() => { + URL.revokeObjectURL(url); + }, 1000); + }; + } +} + +async function saveGeneratedTemplate() { + if (!window.generatedTemplateData) { + alert('没有可保存的模板数据'); + return; + } + + try { + const response = await fetch('/api/global-master-templates/save-generated', { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify(window.generatedTemplateData) + }); + + if (!response.ok) { + const error = await response.json(); + throw new Error(error.detail || 'Failed to save template'); + } + + const result = await response.json(); + console.log('Template saved successfully:', result); + + // 关闭模态框并刷新列表 + closeAIGenerationModal(); + loadTemplates(1); + + alert('模板保存成功!'); + + // 清理临时数据 + delete window.generatedTemplateData; + + } catch (error) { + console.error('Error saving template:', error); + alert('保存模板失败: ' + error.message); + } +} + +async function adjustTemplate() { + const adjustmentInput = document.getElementById('adjustmentInput'); + const adjustmentRequest = adjustmentInput.value.trim(); + + if (!adjustmentRequest) { + alert('请输入调整需求'); + return; + } + + if (!window.generatedTemplateData) { + alert('没有可调整的模板数据'); + return; + } + + try { + // 显示调整进度 + document.getElementById('adjustmentProgress').style.display = 'block'; + document.getElementById('adjustTemplateBtn').disabled = true; + + const requestData = { + html_template: window.generatedTemplateData.html_template, + adjustment_request: adjustmentRequest, + template_name: window.generatedTemplateData.template_name + }; + + const response = await fetch('/api/global-master-templates/adjust-template', { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify(requestData) + }); + + if (!response.ok) { + throw new Error('Failed to adjust template'); + } + + // 处理流式响应 + const reader = response.body.getReader(); + const decoder = new TextDecoder(); + let buffer = ''; + + while (true) { + const { done, value } = await reader.read(); + if (done) break; + + buffer += decoder.decode(value, { stream: true }); + const lines = buffer.split('\n'); + buffer = lines.pop(); + + for (const line of lines) { + if (line.trim() === '') continue; + + try { + if (line.startsWith('data: ')) { + const data = JSON.parse(line.slice(6)); + + if (data.type === 'complete' && data.html_template) { + // 更新模板数据 + window.generatedTemplateData.html_template = data.html_template; + + // 更新预览 + showTemplatePreview(data.html_template); + + // 清空输入框 + adjustmentInput.value = ''; + + alert('模板调整完成!'); + } else if (data.type === 'error') { + // 检查是否是网络错误(502 Bad Gateway等) + let errorMessage = data.message; + if (errorMessage.includes('502') || errorMessage.includes('Bad gateway')) { + errorMessage = 'AI服务暂时不可用,请稍后重试'; + } else if (errorMessage.includes('504') || errorMessage.includes('Gateway Timeout')) { + errorMessage = 'AI服务响应超时,请稍后重试'; + } else if (errorMessage.includes('')) { + errorMessage = 'AI服务连接失败,请检查网络连接或稍后重试'; + } + throw new Error(errorMessage); + } + } + } catch (e) { + console.warn('Failed to parse adjustment stream data:', line, e); + } + } + } + + } catch (error) { + console.error('Error adjusting template:', error); + alert('调整模板失败: ' + error.message); + } finally { + // 隐藏调整进度 + document.getElementById('adjustmentProgress').style.display = 'none'; + document.getElementById('adjustTemplateBtn').disabled = false; + } +} + +async function handleGenerationComplete(data) { + console.log('Handling generation complete with data:', data); + + // 处理不同格式的响应数据 + if (data.html_template) { + // 生成的模板数据格式 + window.generatedTemplateData = { + html_template: data.html_template, + template_name: data.template_name, + description: data.description, + tags: data.tags, + llm_response: data.llm_response // 保存LLM完整响应 + }; + + // 显示预览 + showTemplatePreview(data.html_template); + + // 如果有LLM响应,显示LLM响应数据 + if (data.llm_response) { + displayLLMResponse(data.llm_response); + } + } else if (data.id && data.template_name) { + // 已保存的模板对象格式 + console.log('Template already saved with ID:', data.id); + + // 尝试获取模板的HTML内容来显示预览 + try { + const response = await fetch(`/api/global-master-templates/${data.id}`); + if (response.ok) { + const templateData = await response.json(); + console.log('Fetched template data:', templateData); + + // 检查不同可能的HTML内容字段名 + const htmlContent = templateData.html_content || templateData.content || templateData.html_template; + + if (htmlContent) { + window.generatedTemplateData = { + html_template: htmlContent, + template_name: templateData.name || templateData.template_name, + description: templateData.description, + tags: templateData.tags, + id: templateData.id + }; + + // 显示预览 + showTemplatePreview(htmlContent); + } else { + console.warn('No HTML content found in template data'); + // 显示占位符预览 + showTemplatePreview('
${escapeHtml(code.trim())}`;
+ });
+
+ // 处理行内代码
+ formatted = formatted.replace(/`([^`]+)`/g, '$1');
+
+ // 处理标题
+ formatted = formatted.replace(/^### (.*$)/gm, ''); + formatted = '
' + formatted + '
<\/p>/g, ''); + formatted = formatted.replace(/
\s*<\/p>/g, ''); + + return formatted; +} + +function escapeHtml(text) { + const div = document.createElement('div'); + div.textContent = text; + return div.innerHTML; +}