不知名提交

This commit is contained in:
2025-12-13 20:53:50 +08:00
parent c147502b4d
commit 1221d6faf1
120 changed files with 11005 additions and 1092 deletions

View File

@@ -0,0 +1,29 @@
// 环境配置文件 - AI文章排版
// 复用 InfoGenie 的全局 ENV_CONFIG
// 本地/独立打开页面的API回退地址优先使用父窗口ENV_CONFIG
const DEFAULT_API = (window.ENV_CONFIG && window.ENV_CONFIG.API_URL) || 'http://127.0.0.1:5002';
// API配置
window.API_CONFIG = {
baseUrl: window.parent?.ENV_CONFIG?.API_URL || DEFAULT_API,
endpoints: {
markdownFormatting: '/api/aimodelapp/markdown_formatting'
}
};
// 认证配置
window.AUTH_CONFIG = {
tokenKey: 'token',
getToken: () => localStorage.getItem('token'),
isAuthenticated: () => !!localStorage.getItem('token')
};
// 应用配置
window.APP_CONFIG = {
name: 'InfoGenie AI文章排版',
version: '1.0.0',
debug: false
};
console.log('AI文章排版 环境配置已加载');

View File

@@ -0,0 +1,92 @@
<!doctype html>
<html lang="zh-CN">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>AI文章排版助手</title>
<link rel="stylesheet" href="styles.css">
</head>
<body>
<div class="container">
<div class="header">
<h1 class="title">AI文章排版助手</h1>
<p class="subtitle">保持原文不变 · 智能转为Markdown并点缀Emoji</p>
</div>
<div class="form-section">
<div class="form-group">
<label class="form-label" for="articleText">请输入文章内容:</label>
<textarea
id="articleText"
class="form-input textarea"
placeholder="粘贴或输入原文内容点击开始排版即可生成Markdown并通过Emoji增强可读性..."
></textarea>
</div>
<div class="form-row">
<div class="form-group half-width">
<label class="form-label" for="emojiStyle">Emoji风格</label>
<select id="emojiStyle" class="form-input select">
<option value="balanced">适中(推荐)</option>
<option value="light">清爽少量Emoji</option>
<option value="rich">丰富较多Emoji</option>
</select>
</div>
<div class="form-group half-width">
<label class="form-label" for="markdownOption">排版偏好:</label>
<select id="markdownOption" class="form-input select">
<option value="standard">标准Markdown</option>
<option value="compact">紧凑排版</option>
<option value="readable">易读增强</option>
</select>
</div>
</div>
<button id="formatBtn" class="btn">开始排版</button>
</div>
<div class="result-section">
<h3 class="result-title">排版结果</h3>
<div id="loading" class="loading">正在排版中,请稍候...</div>
<div id="resultContainer" class="conversion-container">
<div class="placeholder">输入文章后点击“开始排版”AI将把原文转换为规范的Markdown并智能添加合适的Emoji</div>
</div>
<div id="previewSection" class="preview-section" style="display:none;">
<div class="preview-header">
<span class="label">Markdown预览</span>
<button class="copy-btn" id="copyHtmlBtn">复制HTML</button>
</div>
<div id="markdownPreview" class="markdown-preview"></div>
</div>
<div id="rawSection" class="raw-section" style="display:none;">
<div class="raw-header">
<span class="label">Markdown源文本</span>
<button class="copy-btn" id="copyMdBtn">复制Markdown</button>
</div>
<pre id="markdownRaw" class="markdown-raw"></pre>
</div>
</div>
</div>
<!-- 环境配置与功能脚本 -->
<script src="env.js"></script>
<!-- Markdown 渲染与安全过滤CDN -->
<script src="https://cdn.jsdelivr.net/npm/marked@4.3.0/marked.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/dompurify@3.0.9/dist/purify.min.js"></script>
<script>
// 检查库是否正确加载
document.addEventListener('DOMContentLoaded', function() {
if (typeof marked === 'undefined') {
console.error('marked库加载失败');
document.getElementById('resultContainer').innerHTML = '<div class="placeholder error">Markdown渲染库加载失败请检查网络连接</div>';
}
if (typeof DOMPurify === 'undefined') {
console.warn('DOMPurify库加载失败将使用不安全的HTML渲染');
}
});
</script>
<script src="script.js"></script>
</body>
</html>

View File

@@ -0,0 +1,152 @@
// 配置已在 env.js 中定义
// DOM元素
const articleTextInput = document.getElementById('articleText');
const emojiStyleSelect = document.getElementById('emojiStyle');
const markdownOptionSelect = document.getElementById('markdownOption');
const formatBtn = document.getElementById('formatBtn');
const loadingDiv = document.getElementById('loading');
const resultContainer = document.getElementById('resultContainer');
const previewSection = document.getElementById('previewSection');
const markdownPreview = document.getElementById('markdownPreview');
const rawSection = document.getElementById('rawSection');
const markdownRaw = document.getElementById('markdownRaw');
const copyMdBtn = document.getElementById('copyMdBtn');
const copyHtmlBtn = document.getElementById('copyHtmlBtn');
// 加载器控制
function showLoading(show) {
loadingDiv.style.display = show ? 'block' : 'none';
formatBtn.disabled = show;
}
// 错误提示
function showErrorMessage(msg) {
resultContainer.innerHTML = `<div class="placeholder">${msg}</div>`;
}
// 调用后端API
async function callBackendAPI(articleText, emojiStyle, markdownOption) {
try {
const token = window.AUTH_CONFIG.getToken();
if (!token) throw new Error('未登录请先登录后使用AI功能');
const url = `${window.API_CONFIG.baseUrl}${window.API_CONFIG.endpoints.markdownFormatting}`;
const response = await fetch(url, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${token}`
},
body: JSON.stringify({
article_text: articleText,
emoji_style: emojiStyle,
markdown_option: markdownOption
})
});
if (!response.ok) {
if (response.status === 402) throw new Error('您的萌芽币余额不足,无法使用此功能');
const errorData = await response.json().catch(() => ({}));
throw new Error(errorData.error || `API请求失败: ${response.status} ${response.statusText}`);
}
const data = await response.json();
if (data.success && data.formatted_markdown) return data.formatted_markdown;
throw new Error(data.error || 'API响应格式异常');
} catch (error) {
console.error('API调用错误:', error);
throw error;
}
}
// 显示结果
function displayFormattingResult(markdownText) {
// 源Markdown
markdownRaw.textContent = markdownText || '';
rawSection.style.display = markdownText ? 'block' : 'none';
// 预览渲染使用marked + DOMPurify
let html = '';
try {
// 兼容新旧版本的marked库
if (typeof marked === 'function') {
// 旧版本marked直接调用
html = marked(markdownText || '');
} else if (marked && typeof marked.parse === 'function') {
// 新版本marked使用parse方法
html = marked.parse(markdownText || '');
} else {
throw new Error('marked库未正确加载');
}
// 使用DOMPurify清理HTML如果可用
const safeHtml = typeof DOMPurify !== 'undefined' ? DOMPurify.sanitize(html) : html;
markdownPreview.innerHTML = safeHtml;
} catch (error) {
console.error('Markdown渲染失败:', error);
markdownPreview.innerHTML = `<div class="error">Markdown渲染失败: ${error.message}</div>`;
}
previewSection.style.display = markdownText ? 'block' : 'none';
// 顶部结果容器状态
resultContainer.innerHTML = '';
resultContainer.classList.add('conversion-result');
}
// 复制功能
function copyToClipboard(text) {
try {
navigator.clipboard.writeText(text);
} catch (e) {
const textarea = document.createElement('textarea');
textarea.value = text;
document.body.appendChild(textarea);
textarea.select();
document.execCommand('copy');
document.body.removeChild(textarea);
}
}
copyMdBtn.addEventListener('click', () => copyToClipboard(markdownRaw.textContent || ''));
copyHtmlBtn.addEventListener('click', () => copyToClipboard(markdownPreview.innerHTML || ''));
// 执行排版
async function performFormatting() {
const articleText = articleTextInput.value.trim();
const emojiStyle = emojiStyleSelect.value;
const markdownOption = markdownOptionSelect.value;
if (!articleText) {
showErrorMessage('请输入需要排版的文章内容');
return;
}
showLoading(true);
resultContainer.innerHTML = '';
previewSection.style.display = 'none';
rawSection.style.display = 'none';
try {
const markdown = await callBackendAPI(articleText, emojiStyle, markdownOption);
displayFormattingResult(markdown);
} catch (error) {
console.error('排版失败:', error);
showErrorMessage(`排版失败: ${error.message}`);
} finally {
showLoading(false);
}
}
// 事件绑定
formatBtn.addEventListener('click', performFormatting);
// 页面初始化
document.addEventListener('DOMContentLoaded', () => {
resultContainer.innerHTML = '<div class="placeholder">请输入文章内容选择Emoji风格与排版偏好然后点击开始排版</div>';
});
// 导出函数供HTML调用
window.performFormatting = performFormatting;
window.copyToClipboard = copyToClipboard;

View File

@@ -0,0 +1,84 @@
/* 全局样式重置 */
* { margin: 0; padding: 0; box-sizing: border-box; }
/* 主体样式 - 清新渐变 */
body {
font-family: -apple-system, BlinkMacSystemFont, 'SF Pro Display', 'Helvetica Neue', Arial, sans-serif;
background: linear-gradient(135deg, #a8e6cf 0%, #dcedc8 50%, #f1f8e9 100%);
min-height: 100vh;
padding: 20px;
color: #2e7d32;
line-height: 1.47;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
/* 容器样式 - 毛玻璃效果 */
.container {
max-width: 900px;
margin: 0 auto;
background: rgba(255, 255, 255, 0.85);
border-radius: 24px;
padding: 32px;
box-shadow: 0 8px 32px rgba(76, 175, 80, 0.15), 0 2px 8px rgba(76, 175, 80, 0.1);
backdrop-filter: blur(20px) saturate(180%);
border: 1px solid rgba(76, 175, 80, 0.2);
}
/* 头部样式 */
.header { text-align: center; margin-bottom: 32px; }
.title { font-size: 2.25rem; color: #1b5e20; margin-bottom: 8px; font-weight: 600; letter-spacing: -0.02em; }
.subtitle { color: #4caf50; font-size: 1.0625rem; margin-bottom: 24px; font-weight: 400; }
/* 表单区域 */
.form-section { margin-bottom: 32px; }
.form-group { margin-bottom: 24px; }
.form-row { display: flex; gap: 16px; margin-bottom: 24px; }
.half-width { flex: 1; }
.form-label { display: block; margin-bottom: 8px; font-weight: 600; color: #2e7d32; }
.form-input { width: 100%; border: 1px solid rgba(0, 0, 0, 0.08); border-radius: 12px; padding: 12px 14px; outline: none; background: rgba(255, 255, 255, 0.75); color: #1b5e20; font-size: 1rem; transition: all 0.2s ease; }
.form-input:focus { border-color: rgba(76, 175, 80, 0.4); box-shadow: 0 0 0 4px rgba(76, 175, 80, 0.15); }
.textarea { min-height: 160px; resize: vertical; line-height: 1.6; }
.select { appearance: none; background-image: linear-gradient(135deg, #f1f8e9 0%, #e8f5e9 100%); }
/* 操作按钮 */
.btn { width: 100%; padding: 14px 18px; border: none; border-radius: 14px; font-weight: 600; font-size: 1.0625rem; color: #fff; background: linear-gradient(135deg, #43a047 0%, #66bb6a 50%, #81c784 100%); box-shadow: 0 4px 16px rgba(76, 175, 80, 0.3), 0 2px 8px rgba(76, 175, 80, 0.2); cursor: pointer; transition: all 0.2s ease; }
.btn:hover { transform: translateY(-1px); box-shadow: 0 6px 20px rgba(76, 175, 80, 0.35); }
.btn:active { transform: translateY(0); background: linear-gradient(135deg, #2e7d32 0%, #388e3c 100%); }
.btn:disabled { opacity: 0.5; cursor: not-allowed; transform: none; background: #86868B; }
/* 结果区域 */
.result-section { margin-top: 32px; }
.result-title { font-size: 1.25rem; color: #1b5e20; margin-bottom: 16px; text-align: center; font-weight: 600; }
.loading { display: none; text-align: center; color: #4caf50; padding: 24px; font-weight: 500; }
.conversion-container { background: rgba(255, 255, 255, 0.6); border: 1px solid rgba(0, 0, 0, 0.08); border-radius: 16px; padding: 24px; min-height: 140px; backdrop-filter: blur(10px); }
.placeholder { text-align: center; color: #86868B; padding: 32px 20px; font-weight: 400; }
.placeholder.error { color: #d32f2f; background: rgba(244, 67, 54, 0.1); border: 1px solid rgba(244, 67, 54, 0.2); border-radius: 8px; }
.error { color: #d32f2f; background: rgba(244, 67, 54, 0.1); padding: 12px; border-radius: 8px; border: 1px solid rgba(244, 67, 54, 0.2); }
/* 预览与原文区域 */
.preview-section, .raw-section { margin-top: 24px; }
.preview-header, .raw-header { display: flex; justify-content: space-between; align-items: center; margin-bottom: 12px; }
.preview-header .label, .raw-header .label { font-weight: 600; color: #2e7d32; font-size: 1rem; }
.copy-btn { padding: 6px 10px; border: none; border-radius: 10px; font-weight: 600; font-size: 0.9375rem; color: #fff; background: linear-gradient(135deg, #4caf50 0%, #81c784 100%); box-shadow: 0 2px 8px rgba(76, 175, 80, 0.25); cursor: pointer; }
.copy-btn:hover { filter: brightness(1.05); }
.markdown-preview { background: rgba(255, 255, 255, 0.9); border: 1px solid rgba(0, 0, 0, 0.06); border-radius: 12px; padding: 20px; color: #2e7d32; line-height: 1.8; }
.markdown-raw { background: rgba(255, 255, 255, 0.85); border: 1px solid rgba(0, 0, 0, 0.06); border-radius: 12px; padding: 16px; color: #1b5e20; font-family: 'SF Mono', 'Monaco', 'Consolas', 'Courier New', monospace; font-size: 0.9375rem; white-space: pre-wrap; word-break: break-word; }
/* Markdown渲染细节 */
.markdown-preview h1, .markdown-preview h2, .markdown-preview h3 { color: #1b5e20; margin: 10px 0; }
.markdown-preview p { margin: 10px 0; }
.markdown-preview ul, .markdown-preview ol { padding-left: 24px; margin: 10px 0; }
.markdown-preview blockquote { border-left: 4px solid rgba(76, 175, 80, 0.4); padding-left: 12px; color: #4caf50; background: rgba(76, 175, 80, 0.08); border-radius: 6px; }
.markdown-preview code { background: rgba(0, 0, 0, 0.06); padding: 2px 6px; border-radius: 6px; font-family: 'SF Mono', 'Monaco', 'Consolas', 'Courier New', monospace; }
/* 移动端优化 */
@media (max-width: 480px) {
.container { padding: 18px; border-radius: 18px; }
.title { font-size: 1.75rem; }
.subtitle { font-size: 0.95rem; }
.form-row { flex-direction: column; gap: 12px; }
.textarea { min-height: 200px; }
.btn { font-size: 1rem; padding: 12px 16px; }
}