不知名提交
This commit is contained in:
25
InfoGenie-frontend/public/aimodelapp/AI中国亲戚称呼计算器/env.js
Normal file
25
InfoGenie-frontend/public/aimodelapp/AI中国亲戚称呼计算器/env.js
Normal file
@@ -0,0 +1,25 @@
|
||||
// 环境配置文件 - AI中国亲戚称呼计算器
|
||||
// 复用 InfoGenie 的全局 ENV_CONFIG,支持独立打开的回退地址
|
||||
|
||||
const DEFAULT_API = (window.ENV_CONFIG && window.ENV_CONFIG.API_URL) || 'http://127.0.0.1:5002';
|
||||
|
||||
window.API_CONFIG = {
|
||||
baseUrl: window.parent?.ENV_CONFIG?.API_URL || DEFAULT_API,
|
||||
endpoints: {
|
||||
kinshipCalculator: '/api/aimodelapp/kinship-calculator'
|
||||
}
|
||||
};
|
||||
|
||||
window.AUTH_CONFIG = {
|
||||
tokenKey: 'token',
|
||||
getToken: () => localStorage.getItem('token'),
|
||||
isAuthenticated: () => !!localStorage.getItem('token')
|
||||
};
|
||||
|
||||
window.APP_CONFIG = {
|
||||
name: 'InfoGenie 中国亲戚称呼计算器',
|
||||
version: '1.0.0',
|
||||
debug: false
|
||||
};
|
||||
|
||||
console.log('中国亲戚称呼计算器 环境配置已加载');
|
||||
59
InfoGenie-frontend/public/aimodelapp/AI中国亲戚称呼计算器/index.html
Normal file
59
InfoGenie-frontend/public/aimodelapp/AI中国亲戚称呼计算器/index.html
Normal file
@@ -0,0 +1,59 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1" />
|
||||
<title>中国亲戚称呼计算器</title>
|
||||
<link rel="stylesheet" href="styles.css" />
|
||||
</head>
|
||||
<body>
|
||||
<div class="app-container">
|
||||
<header class="app-header">
|
||||
<h1>中国亲戚称呼计算器</h1>
|
||||
<p class="subtitle">输入亲属关系链(如“妈妈的爸爸”、“爸爸的姐姐的儿子”),快速得到标准普通话称呼与各地方言称呼</p>
|
||||
</header>
|
||||
|
||||
<main class="card">
|
||||
<section class="input-section">
|
||||
<label for="relationInput" class="label">亲属关系链</label>
|
||||
<textarea id="relationInput" class="textarea" rows="2" placeholder="例如:妈妈的爸爸"></textarea>
|
||||
|
||||
<div class="hint">
|
||||
· 使用“的”连接每一层关系,例如:
|
||||
<span class="chip" onclick="setExample('妈妈的爸爸')">妈妈的爸爸</span>
|
||||
<span class="chip" onclick="setExample('爸爸的姐姐的儿子')">爸爸的姐姐的儿子</span>
|
||||
<span class="chip" onclick="setExample('妈妈的弟弟的女儿')">妈妈的弟弟的女儿</span>
|
||||
</div>
|
||||
|
||||
<button id="calcBtn" class="button primary">计算称呼</button>
|
||||
<div id="loading" class="loading" style="display:none">正在计算,请稍候…</div>
|
||||
<div id="error" class="error" style="display:none"></div>
|
||||
</section>
|
||||
|
||||
<section id="resultSection" class="result-section" style="display:none">
|
||||
<h2>计算结果</h2>
|
||||
<div class="result-block">
|
||||
<div class="result-title">标准普通话称呼</div>
|
||||
<div id="mandarinTitle" class="result-value"></div>
|
||||
<div class="actions">
|
||||
<button id="copyMandarinBtn" class="button">复制称呼</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="result-block">
|
||||
<div class="result-title">各地方言称呼</div>
|
||||
<div id="dialectList" class="dialect-list"></div>
|
||||
</div>
|
||||
|
||||
<div id="notesBlock" class="result-block" style="display:none">
|
||||
<div class="result-title">说明</div>
|
||||
<div id="notes" class="notes"></div>
|
||||
</div>
|
||||
</section>
|
||||
</main>
|
||||
</div>
|
||||
|
||||
<script src="env.js"></script>
|
||||
<script src="script.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
135
InfoGenie-frontend/public/aimodelapp/AI中国亲戚称呼计算器/script.js
Normal file
135
InfoGenie-frontend/public/aimodelapp/AI中国亲戚称呼计算器/script.js
Normal file
@@ -0,0 +1,135 @@
|
||||
// 环境与认证在 env.js 中定义
|
||||
|
||||
const relationInput = document.getElementById('relationInput');
|
||||
const calcBtn = document.getElementById('calcBtn');
|
||||
const loadingDiv = document.getElementById('loading');
|
||||
const errorDiv = document.getElementById('error');
|
||||
const resultSection = document.getElementById('resultSection');
|
||||
const mandarinTitleEl = document.getElementById('mandarinTitle');
|
||||
const dialectListEl = document.getElementById('dialectList');
|
||||
const copyMandarinBtn = document.getElementById('copyMandarinBtn');
|
||||
const notesBlock = document.getElementById('notesBlock');
|
||||
const notesEl = document.getElementById('notes');
|
||||
|
||||
function setExample(text) {
|
||||
relationInput.value = text;
|
||||
}
|
||||
window.setExample = setExample;
|
||||
|
||||
function showLoading(show) {
|
||||
loadingDiv.style.display = show ? 'block' : 'none';
|
||||
calcBtn.disabled = show;
|
||||
}
|
||||
|
||||
function showError(msg) {
|
||||
errorDiv.textContent = msg || '';
|
||||
errorDiv.style.display = msg ? 'block' : 'none';
|
||||
}
|
||||
|
||||
function clearResults() {
|
||||
resultSection.style.display = 'none';
|
||||
mandarinTitleEl.textContent = '';
|
||||
dialectListEl.innerHTML = '';
|
||||
notesBlock.style.display = 'none';
|
||||
notesEl.textContent = '';
|
||||
}
|
||||
|
||||
async function callKinshipAPI(relationChain) {
|
||||
const token = window.AUTH_CONFIG.getToken();
|
||||
if (!token) throw new Error('未登录,请先登录后使用AI功能');
|
||||
|
||||
const url = `${window.API_CONFIG.baseUrl}${window.API_CONFIG.endpoints.kinshipCalculator}`;
|
||||
const resp = await fetch(url, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'Authorization': `Bearer ${token}`
|
||||
},
|
||||
body: JSON.stringify({ relation_chain: relationChain })
|
||||
});
|
||||
|
||||
if (!resp.ok) {
|
||||
if (resp.status === 402) throw new Error('您的萌芽币余额不足,无法使用此功能');
|
||||
const err = await resp.json().catch(() => ({}));
|
||||
throw new Error(err.error || `API请求失败: ${resp.status} ${resp.statusText}`);
|
||||
}
|
||||
|
||||
const data = await resp.json();
|
||||
if (!data.success) throw new Error(data.error || 'API响应异常');
|
||||
return data;
|
||||
}
|
||||
|
||||
function renderDialects(dialectTitles) {
|
||||
dialectListEl.innerHTML = '';
|
||||
const order = ['粤语','闽南语','上海话','四川话','东北话','客家话'];
|
||||
const names = order.concat(Object.keys(dialectTitles || {}).filter(k => !order.includes(k)));
|
||||
|
||||
names.forEach(name => {
|
||||
const info = dialectTitles?.[name];
|
||||
if (!info || (!info.title && !info.romanization && !info.notes)) return;
|
||||
const item = document.createElement('div');
|
||||
item.className = 'dialect-item';
|
||||
const title = (info.title || '').toString();
|
||||
const roman = (info.romanization || '').toString();
|
||||
const notes = (info.notes || '').toString();
|
||||
item.innerHTML = `
|
||||
<div class="dialect-name">${name}</div>
|
||||
<div class="dialect-title">${title}</div>
|
||||
${roman ? `<div class="dialect-roman">${roman}</div>` : ''}
|
||||
${notes ? `<div class="dialect-notes">${notes}</div>` : ''}
|
||||
`;
|
||||
dialectListEl.appendChild(item);
|
||||
});
|
||||
}
|
||||
|
||||
async function doCalculate() {
|
||||
const relation = (relationInput.value || '').trim();
|
||||
if (!relation) {
|
||||
showError('请输入亲属关系链');
|
||||
return;
|
||||
}
|
||||
showError('');
|
||||
showLoading(true);
|
||||
clearResults();
|
||||
|
||||
try {
|
||||
const data = await callKinshipAPI(relation);
|
||||
mandarinTitleEl.textContent = data.mandarin_title || '';
|
||||
renderDialects(data.dialect_titles || {});
|
||||
if (data.notes) {
|
||||
notesEl.textContent = data.notes;
|
||||
notesBlock.style.display = 'block';
|
||||
}
|
||||
resultSection.style.display = 'block';
|
||||
} catch (e) {
|
||||
console.error('计算失败:', e);
|
||||
showError(`计算失败: ${e.message}`);
|
||||
} finally {
|
||||
showLoading(false);
|
||||
}
|
||||
}
|
||||
|
||||
function copyText(text) {
|
||||
try {
|
||||
navigator.clipboard.writeText(text);
|
||||
} catch (e) {
|
||||
const ta = document.createElement('textarea');
|
||||
ta.value = text;
|
||||
document.body.appendChild(ta);
|
||||
ta.select();
|
||||
document.execCommand('copy');
|
||||
document.body.removeChild(ta);
|
||||
}
|
||||
}
|
||||
|
||||
copyMandarinBtn.addEventListener('click', () => {
|
||||
const t = mandarinTitleEl.textContent || '';
|
||||
if (!t) return;
|
||||
copyText(t);
|
||||
});
|
||||
|
||||
calcBtn.addEventListener('click', doCalculate);
|
||||
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
showError('');
|
||||
});
|
||||
189
InfoGenie-frontend/public/aimodelapp/AI中国亲戚称呼计算器/styles.css
Normal file
189
InfoGenie-frontend/public/aimodelapp/AI中国亲戚称呼计算器/styles.css
Normal file
@@ -0,0 +1,189 @@
|
||||
/* 渐变背景与毛玻璃风格,参考 AI文章排版 */
|
||||
:root {
|
||||
--green: #a8e6cf;
|
||||
--lime: #dcedc1;
|
||||
--dark: #2e7d32;
|
||||
--text: #1b5e20;
|
||||
--muted: #558b2f;
|
||||
--white: #ffffff;
|
||||
--shadow: rgba(0, 0, 0, 0.08);
|
||||
}
|
||||
|
||||
html, body {
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
body {
|
||||
margin: 0;
|
||||
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, "Noto Sans", "PingFang SC", "Microsoft YaHei", sans-serif;
|
||||
color: var(--text);
|
||||
background: linear-gradient(135deg, var(--green) 0%, var(--lime) 100%);
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
}
|
||||
|
||||
.app-container {
|
||||
min-height: 100vh;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
padding: 16px;
|
||||
}
|
||||
|
||||
.app-header {
|
||||
text-align: center;
|
||||
margin: 8px 0 16px;
|
||||
}
|
||||
|
||||
.app-header h1 {
|
||||
font-size: 22px;
|
||||
margin: 0;
|
||||
color: var(--dark);
|
||||
}
|
||||
|
||||
.subtitle {
|
||||
margin-top: 8px;
|
||||
font-size: 13px;
|
||||
color: var(--muted);
|
||||
}
|
||||
|
||||
.card {
|
||||
width: 100%;
|
||||
max-width: 720px;
|
||||
background: rgba(255,255,255,0.75);
|
||||
backdrop-filter: blur(10px);
|
||||
border-radius: 14px;
|
||||
box-shadow: 0 8px 24px var(--shadow);
|
||||
padding: 16px;
|
||||
}
|
||||
|
||||
.label {
|
||||
display: block;
|
||||
font-size: 13px;
|
||||
color: var(--muted);
|
||||
margin-bottom: 6px;
|
||||
}
|
||||
|
||||
.textarea {
|
||||
width: 100%;
|
||||
box-sizing: border-box;
|
||||
border: 1px solid rgba(0,0,0,0.05);
|
||||
border-radius: 10px;
|
||||
padding: 10px 12px;
|
||||
font-size: 14px;
|
||||
color: var(--text);
|
||||
outline: none;
|
||||
}
|
||||
.textarea:focus {
|
||||
border-color: rgba(46,125,50,0.35);
|
||||
box-shadow: 0 0 0 3px rgba(46,125,50,0.12);
|
||||
}
|
||||
|
||||
.hint {
|
||||
margin: 10px 0 12px;
|
||||
font-size: 12px;
|
||||
color: var(--muted);
|
||||
}
|
||||
.chip {
|
||||
display: inline-block;
|
||||
background: rgba(255,255,255,0.9);
|
||||
border: 1px solid rgba(46,125,50,0.2);
|
||||
color: var(--dark);
|
||||
border-radius: 999px;
|
||||
padding: 4px 10px;
|
||||
margin-right: 6px;
|
||||
cursor: pointer;
|
||||
user-select: none;
|
||||
}
|
||||
.chip:hover { filter: brightness(0.98); }
|
||||
|
||||
.button {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
border: none;
|
||||
border-radius: 12px;
|
||||
padding: 10px 14px;
|
||||
font-size: 14px;
|
||||
cursor: pointer;
|
||||
background: rgba(46,125,50,0.15);
|
||||
color: var(--dark);
|
||||
}
|
||||
.button.primary {
|
||||
background: linear-gradient(135deg, #81c784, #aed581);
|
||||
color: #fff;
|
||||
}
|
||||
.button:disabled { opacity: 0.6; cursor: not-allowed; }
|
||||
|
||||
.loading {
|
||||
margin-top: 10px;
|
||||
font-size: 13px;
|
||||
color: var(--muted);
|
||||
}
|
||||
.error {
|
||||
margin-top: 8px;
|
||||
padding: 8px 10px;
|
||||
border-left: 3px solid #e53935;
|
||||
background: rgba(229,57,53,0.08);
|
||||
border-radius: 8px;
|
||||
color: #c62828;
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
.result-section { margin-top: 14px; }
|
||||
.result-section h2 {
|
||||
font-size: 16px;
|
||||
margin: 0 0 8px;
|
||||
color: var(--dark);
|
||||
}
|
||||
|
||||
.result-block {
|
||||
background: rgba(255,255,255,0.9);
|
||||
border: 1px solid rgba(0,0,0,0.05);
|
||||
border-radius: 12px;
|
||||
padding: 10px;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
.result-title {
|
||||
font-size: 13px;
|
||||
color: var(--muted);
|
||||
margin-bottom: 6px;
|
||||
}
|
||||
.result-value {
|
||||
font-size: 18px;
|
||||
color: var(--text);
|
||||
}
|
||||
.actions { margin-top: 8px; }
|
||||
|
||||
.dialect-list {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr;
|
||||
gap: 8px;
|
||||
}
|
||||
@media (min-width: 540px) {
|
||||
.dialect-list { grid-template-columns: 1fr 1fr; }
|
||||
}
|
||||
|
||||
.dialect-item {
|
||||
border: 1px solid rgba(46,125,50,0.18);
|
||||
border-radius: 10px;
|
||||
padding: 8px;
|
||||
background: rgba(255,255,255,0.95);
|
||||
}
|
||||
.dialect-item .dialect-name {
|
||||
font-weight: 600;
|
||||
color: var(--dark);
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
.dialect-item .dialect-title { font-size: 15px; }
|
||||
.dialect-item .dialect-roman { font-size: 12px; color: var(--muted); }
|
||||
.dialect-item .dialect-notes { font-size: 12px; color: var(--muted); margin-top: 4px; }
|
||||
|
||||
.notes { font-size: 13px; color: var(--muted); }
|
||||
|
||||
.app-footer {
|
||||
margin-top: 12px;
|
||||
font-size: 12px;
|
||||
color: var(--muted);
|
||||
text-align: center;
|
||||
}
|
||||
@@ -8,13 +8,31 @@
|
||||
/* 主体样式 - iOS风格 */
|
||||
body {
|
||||
font-family: -apple-system, BlinkMacSystemFont, 'SF Pro Display', 'Helvetica Neue', Arial, sans-serif;
|
||||
background: linear-gradient(135deg, #87CEEB 0%, #98FB98 100%);
|
||||
background: linear-gradient(135deg, #F0FFF0 0%, #98FB98 50%, #90EE90 100%);
|
||||
min-height: 100vh;
|
||||
padding: 20px;
|
||||
color: #1D1D1F;
|
||||
line-height: 1.47;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
/* 隐藏滚动条但保留滚动功能 */
|
||||
scrollbar-width: none; /* Firefox */
|
||||
-ms-overflow-style: none; /* IE/Edge */
|
||||
}
|
||||
|
||||
/* 隐藏Webkit浏览器的滚动条 */
|
||||
body::-webkit-scrollbar {
|
||||
display: none;
|
||||
}
|
||||
|
||||
/* 全局滚动条隐藏 */
|
||||
* {
|
||||
scrollbar-width: none; /* Firefox */
|
||||
-ms-overflow-style: none; /* IE/Edge */
|
||||
}
|
||||
|
||||
*::-webkit-scrollbar {
|
||||
display: none; /* Chrome, Safari, Opera */
|
||||
}
|
||||
|
||||
/* 容器样式 - iOS毛玻璃效果 */
|
||||
@@ -81,9 +99,9 @@ body {
|
||||
|
||||
.form-input:focus {
|
||||
outline: none;
|
||||
border-color: #007AFF;
|
||||
border-color: #32CD32;
|
||||
background: rgba(255, 255, 255, 0.95);
|
||||
box-shadow: 0 0 0 4px rgba(0, 122, 255, 0.1);
|
||||
box-shadow: 0 0 0 4px rgba(50, 205, 50, 0.1);
|
||||
}
|
||||
|
||||
.textarea {
|
||||
@@ -105,7 +123,7 @@ body {
|
||||
.btn {
|
||||
width: 100%;
|
||||
padding: 16px;
|
||||
background: #007AFF;
|
||||
background: #32CD32;
|
||||
color: white;
|
||||
border: none;
|
||||
border-radius: 12px;
|
||||
@@ -114,18 +132,18 @@ body {
|
||||
cursor: pointer;
|
||||
transition: all 0.2s ease;
|
||||
margin-bottom: 24px;
|
||||
box-shadow: 0 2px 8px rgba(0, 122, 255, 0.25);
|
||||
box-shadow: 0 2px 8px rgba(50, 205, 50, 0.25);
|
||||
}
|
||||
|
||||
.btn:hover {
|
||||
background: #0056CC;
|
||||
background: #228B22;
|
||||
transform: translateY(-1px);
|
||||
box-shadow: 0 4px 16px rgba(0, 122, 255, 0.35);
|
||||
box-shadow: 0 4px 16px rgba(50, 205, 50, 0.35);
|
||||
}
|
||||
|
||||
.btn:active {
|
||||
transform: translateY(0);
|
||||
background: #004499;
|
||||
background: #006400;
|
||||
}
|
||||
|
||||
.btn:disabled {
|
||||
@@ -151,7 +169,7 @@ body {
|
||||
.loading {
|
||||
display: none;
|
||||
text-align: center;
|
||||
color: #007AFF;
|
||||
color: #32CD32;
|
||||
font-style: normal;
|
||||
padding: 24px;
|
||||
font-weight: 500;
|
||||
@@ -161,9 +179,12 @@ body {
|
||||
background: rgba(255, 255, 255, 0.6);
|
||||
border: 1px solid rgba(0, 0, 0, 0.08);
|
||||
border-radius: 16px;
|
||||
padding: 24px;
|
||||
padding: 20px;
|
||||
min-height: 150px;
|
||||
backdrop-filter: blur(10px);
|
||||
display: grid;
|
||||
gap: 16px;
|
||||
grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
|
||||
}
|
||||
|
||||
.placeholder {
|
||||
@@ -176,15 +197,16 @@ body {
|
||||
|
||||
/* 分组标题样式 - iOS风格 */
|
||||
.convention-group-title {
|
||||
font-size: 1.0625rem;
|
||||
font-size: 1rem;
|
||||
font-weight: 600;
|
||||
color: white;
|
||||
margin: 20px 0 12px 0;
|
||||
padding: 12px 16px;
|
||||
background: #007AFF;
|
||||
margin: 16px 0 12px 0;
|
||||
padding: 10px 16px;
|
||||
background: #32CD32;
|
||||
border-radius: 12px;
|
||||
text-align: center;
|
||||
box-shadow: 0 2px 8px rgba(0, 122, 255, 0.25);
|
||||
box-shadow: 0 2px 8px rgba(50, 205, 50, 0.25);
|
||||
grid-column: 1 / -1;
|
||||
}
|
||||
|
||||
.convention-group-title:first-child {
|
||||
@@ -196,17 +218,21 @@ body {
|
||||
background: rgba(255, 255, 255, 0.9);
|
||||
border: 1px solid rgba(0, 0, 0, 0.06);
|
||||
border-radius: 12px;
|
||||
padding: 16px;
|
||||
margin-bottom: 12px;
|
||||
padding: 12px;
|
||||
margin-bottom: 0;
|
||||
transition: all 0.2s ease;
|
||||
cursor: pointer;
|
||||
position: relative;
|
||||
backdrop-filter: blur(10px);
|
||||
min-height: 80px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.suggestion-item:hover {
|
||||
border-color: rgba(0, 122, 255, 0.3);
|
||||
box-shadow: 0 4px 16px rgba(0, 122, 255, 0.1);
|
||||
border-color: rgba(50, 205, 50, 0.3);
|
||||
box-shadow: 0 4px 16px rgba(50, 205, 50, 0.1);
|
||||
background: rgba(255, 255, 255, 0.95);
|
||||
}
|
||||
|
||||
@@ -216,33 +242,35 @@ body {
|
||||
|
||||
.variable-name {
|
||||
font-family: 'SF Mono', 'Monaco', 'Consolas', 'Courier New', monospace;
|
||||
font-size: 1.0625rem;
|
||||
font-size: 1rem;
|
||||
font-weight: 600;
|
||||
color: #1D1D1F;
|
||||
margin-bottom: 6px;
|
||||
margin-bottom: 4px;
|
||||
word-break: break-all;
|
||||
}
|
||||
|
||||
.variable-description {
|
||||
font-size: 0.9375rem;
|
||||
font-size: 0.875rem;
|
||||
color: #86868B;
|
||||
line-height: 1.47;
|
||||
line-height: 1.4;
|
||||
flex-grow: 1;
|
||||
}
|
||||
|
||||
.copy-btn {
|
||||
position: absolute;
|
||||
top: 12px;
|
||||
right: 12px;
|
||||
background: #007AFF;
|
||||
top: 8px;
|
||||
right: 8px;
|
||||
background: #32CD32;
|
||||
color: white;
|
||||
border: none;
|
||||
border-radius: 8px;
|
||||
padding: 6px 12px;
|
||||
font-size: 0.8125rem;
|
||||
border-radius: 6px;
|
||||
padding: 4px 8px;
|
||||
font-size: 0.75rem;
|
||||
font-weight: 600;
|
||||
cursor: pointer;
|
||||
opacity: 0;
|
||||
transition: all 0.2s ease;
|
||||
box-shadow: 0 2px 4px rgba(0, 122, 255, 0.25);
|
||||
box-shadow: 0 1px 3px rgba(50, 205, 50, 0.25);
|
||||
}
|
||||
|
||||
.suggestion-item:hover .copy-btn {
|
||||
@@ -250,7 +278,7 @@ body {
|
||||
}
|
||||
|
||||
.copy-btn:hover {
|
||||
background: #0056CC;
|
||||
background: #228B22;
|
||||
transform: translateY(-1px);
|
||||
}
|
||||
|
||||
@@ -314,17 +342,30 @@ body {
|
||||
|
||||
.suggestions-container {
|
||||
padding: 15px;
|
||||
grid-template-columns: 1fr;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.suggestion-item {
|
||||
padding: 12px;
|
||||
padding: 10px;
|
||||
min-height: 70px;
|
||||
}
|
||||
|
||||
.copy-btn {
|
||||
position: static;
|
||||
opacity: 1;
|
||||
margin-top: 8px;
|
||||
margin-top: 6px;
|
||||
width: 100%;
|
||||
padding: 6px 8px;
|
||||
font-size: 0.75rem;
|
||||
}
|
||||
|
||||
.variable-name {
|
||||
font-size: 0.9rem;
|
||||
}
|
||||
|
||||
.variable-description {
|
||||
font-size: 0.8rem;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -342,16 +383,30 @@ body {
|
||||
padding: 10px;
|
||||
}
|
||||
|
||||
.suggestions-container {
|
||||
padding: 12px;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.suggestion-item {
|
||||
padding: 10px;
|
||||
padding: 8px;
|
||||
min-height: 60px;
|
||||
}
|
||||
|
||||
.variable-name {
|
||||
font-size: 1rem;
|
||||
font-size: 0.85rem;
|
||||
margin-bottom: 3px;
|
||||
}
|
||||
|
||||
.variable-description {
|
||||
font-size: 0.85rem;
|
||||
font-size: 0.75rem;
|
||||
line-height: 1.3;
|
||||
}
|
||||
|
||||
.convention-group-title {
|
||||
font-size: 0.9rem;
|
||||
padding: 8px 12px;
|
||||
margin: 12px 0 8px 0;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
29
InfoGenie-frontend/public/aimodelapp/AI文章排版/env.js
Normal file
29
InfoGenie-frontend/public/aimodelapp/AI文章排版/env.js
Normal 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文章排版 环境配置已加载');
|
||||
92
InfoGenie-frontend/public/aimodelapp/AI文章排版/index.html
Normal file
92
InfoGenie-frontend/public/aimodelapp/AI文章排版/index.html
Normal 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>
|
||||
152
InfoGenie-frontend/public/aimodelapp/AI文章排版/script.js
Normal file
152
InfoGenie-frontend/public/aimodelapp/AI文章排版/script.js
Normal 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;
|
||||
84
InfoGenie-frontend/public/aimodelapp/AI文章排版/styles.css
Normal file
84
InfoGenie-frontend/public/aimodelapp/AI文章排版/styles.css
Normal 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; }
|
||||
}
|
||||
Reference in New Issue
Block a user