abo/news_agent/templates/index.html
2025-07-16 15:34:54 +08:00

333 lines
12 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<!doctype html>
<html>
<head>
<title>ABO Agent</title>
<meta name="viewport" content="width=device-width, initial-scale=1">
<style>
html, body {
height: 100%;
margin: 0;
padding: 0;
}
body {
font-family: 'Segoe UI', Arial, sans-serif;
background-color: #f5f7fa;
min-height: 100vh;
}
.main-container {
display: flex;
flex-direction: row;
justify-content: center;
align-items: stretch;
gap: 24px;
margin: 48px auto 0 auto;
max-width: 1500px;
width: 98vw;
min-height: 600px;
}
.left-form, .right-preview, .api-info-panel {
background: #fff;
padding: 36px 24px 32px 24px;
border-radius: 14px;
box-shadow: 0 4px 24px rgba(0,0,0,0.10);
display: flex;
flex-direction: column;
min-height: 600px;
}
.left-form { flex: 0 0 40%; max-width: 40vw; }
.right-preview {
flex: 0 0 40%;
max-width: 40vw;
display: flex;
flex-direction: column;
height: 100%;
}
.api-info-panel { flex: 0 0 20%; max-width: 20vw; }
h1 {
color: #222;
text-align: center;
margin-bottom: 36px;
font-size: 2.3rem;
letter-spacing: 2px;
}
.form-group {
margin-bottom: 28px;
}
label, .preview-label {
display: block;
margin-bottom: 8px;
font-weight: 600;
color: #444;
font-size: 1.08rem;
}
textarea, select, input[type="file"] {
width: 100%;
padding: 14px;
border: 1.5px solid #e0e0e0;
border-radius: 6px;
font-size: 1.08rem;
box-sizing: border-box;
background: #fafbfc;
transition: border 0.2s;
}
textarea:focus, select:focus, input[type="file"]:focus {
border: 1.5px solid #007bff;
outline: none;
}
textarea {
resize: vertical;
min-height: 120px;
font-family: inherit;
}
.model-info {
background: #f4f6fa;
padding: 12px 10px;
border-radius: 6px;
margin-top: 8px;
font-size: 0.98rem;
color: #666;
}
.btn-center {
display: flex;
justify-content: center;
align-items: center;
width: 100%;
}
.submit-btn {
background: #007bff;
color: white;
padding: 16px 0;
border: none;
border-radius: 6px;
cursor: pointer;
font-size: 1.15rem;
font-weight: 600;
margin-top: 10px;
transition: background 0.2s;
width: 200px;
text-align: center;
}
.submit-btn:disabled {
background: #b3c6e0;
color: #fff;
cursor: not-allowed;
}
.api-key-note {
background: #fffbe6;
border: 1.5px solid #ffe58f;
padding: 14px 16px;
border-radius: 7px;
margin-bottom: 28px;
font-size: 1.02rem;
}
select option:disabled {
color: #bbb;
font-style: italic;
}
.model-status {
display: inline-block;
padding: 2px 6px;
border-radius: 3px;
font-size: 12px;
font-weight: bold;
}
.status-ok {
background: #d4edda;
color: #155724;
}
.status-error {
background: #f8d7da;
color: #721c24;
}
.preview-label {
font-weight: 600;
color: #444;
margin-bottom: 10px;
font-size: 1.08rem;
}
.preview-textarea {
width: 100%;
flex: 1 1 0;
min-height: 0;
height: auto;
font-size: 1.08rem;
border: 1.5px solid #e0e0e0;
border-radius: 6px;
padding: 14px;
margin-bottom: 18px;
background: #f8f9fa;
font-family: inherit;
box-sizing: border-box;
transition: border 0.2s;
resize: vertical;
}
.preview-textarea:focus {
border: 1.5px solid #007bff;
}
/* 滚动条美化 */
::-webkit-scrollbar {
width: 8px;
background: #f4f6fa;
}
::-webkit-scrollbar-thumb {
background: #e0e0e0;
border-radius: 4px;
}
/* 响应式布局 */
@media (max-width: 1100px) {
.main-container {
flex-direction: column;
max-width: 98vw;
gap: 24px;
}
.left-form, .right-preview {
min-width: unset;
max-width: unset;
width: 100%;
height: auto;
}
}
@media (max-width: 1300px) {
.main-container { flex-direction: column; }
.left-form, .right-preview, .api-info-panel { min-width: unset; max-width: unset; width: 100%; margin-top: 0; height: auto; }
}
</style>
<script>
window.onload = function() {
var form = document.getElementById('generateForm');
if(form) {
form.onsubmit = function() {
var btn = document.getElementById('generateBtn');
if(btn) {
btn.disabled = true;
btn.innerText = '生成中...';
}
};
}
};
</script>
</head>
<body>
<!-- 顶部登录状态栏 -->
<div style="width:100%;background:transparent;position:relative;">
<div style="position:absolute;top:18px;right:40px;z-index:10;">
<!-- 显示当前登录用户名和退出按钮 -->
<span style="font-size:15px;color:#333;">已登录:<b>{{ session.get('username', 'admin') }}</b></span>
<a href="{{ url_for('logout') }}" style="margin-left:18px;color:#007bff;text-decoration:none;font-size:15px;">退出登录</a>
</div>
</div>
<h1>ABO Agent</h1>
<div class="main-container">
<div class="left-form">
<div class="api-key-note">
<strong>注意:</strong>使用前请确保已设置相应的API密钥环境变量
<ul>
<li>OpenAI: OPENAI_API_KEY</li>
<li>火山引擎: VOLCENGINE_API_KEY</li>
<li>阿里通义: DASHSCOPE_API_KEY</li>
</ul>
<p style="margin-top: 10px;">
<strong>快速配置:</strong>在终端运行 <code>python setup_config.py</code> 来交互式配置API密钥
</p>
</div>
<form id="generateForm">
<div class="form-group">
<label>选择大模型:</label>
<select name="model" required>
{% for key, model in models.items() %}
<option value="{{ key }}" {% if selected_model == key %}selected{% endif %} {% if not api_key_status[key] %}disabled{% endif %}>
{{ model.name }}
</option>
{% endfor %}
</select>
<div class="model-info">
{% for key, model in models.items() %}
{% if api_key_status[key] %}
<strong style="color: #155724;">✓ {{ model.name }}:</strong>
{% else %}
<strong style="color: #721c24;">✗ {{ model.name }}:</strong>
{% endif %}
使用{{ model.model }}模型<br>
{% endfor %}
</div>
</div>
<div class="form-group">
<label>新闻事件简要描述:</label>
<textarea name="description" rows="5" required placeholder="请描述新闻事件的关键信息..."></textarea>
</div>
<div class="form-group">
<label>话题可选最多3个:</label>
<input type="text" name="topic1" maxlength="20" placeholder="话题1宠物经济">
<input type="text" name="topic2" maxlength="20" placeholder="话题2新零售" style="margin-top:8px;">
<input type="text" name="topic3" maxlength="20" placeholder="话题3品牌升级" style="margin-top:8px;">
</div>
<div class="form-group">
<label>选择文案风格:</label>
<select name="style" required>
{% for style in styles %}
<option value="{{ style }}">{{ style }}</option>
{% endfor %}
</select>
</div>
<div class="form-group">
<label>上传新闻照片 (可选):</label>
<input type="file" name="image" accept="image/*">
</div>
<div class="btn-center">
<button type="button" class="submit-btn" id="generateBtn">开始生成</button>
</div>
</form>
</div>
<div class="right-preview">
<form method="post" enctype="multipart/form-data" style="display: flex; flex-direction: column; height: 100%; flex: 1 1 0;">
<label class="preview-label">新闻稿内容预览(可编辑):</label>
<textarea name="news_content" class="preview-textarea">{{ news_content or '' }}</textarea>
<div class="btn-center">
<button type="submit" name="action" value="download" class="submit-btn">下载Word文档</button>
</div>
</form>
</div>
<div class="api-info-panel" id="apiInfoPanel">
<h3>API调用信息</h3>
<div class="info-item">暂无</div>
</div>
</div>
<script>
document.getElementById('generateBtn').onclick = function() {
var btn = this;
btn.disabled = true;
btn.innerText = '生成中...';
var form = document.getElementById('generateForm');
var formData = new FormData(form);
fetch('/api/generate', {
method: 'POST',
body: formData
})
.then(resp => resp.json())
.then(data => {
document.querySelector('.preview-textarea').value = data.news_content || '';
// 渲染API信息
var info = data.api_info || {};
var html = '<h3>API调用信息</h3>';
var hasInfo = false;
for (var k in info) {
if(info[k] !== undefined && info[k] !== null) {
html += '<div class="info-item"><strong>' + k + '</strong>' + info[k] + '</div>';
hasInfo = true;
}
}
if(!hasInfo) html += '<div class="info-item">暂无</div>';
document.getElementById('apiInfoPanel').innerHTML = html;
})
.catch(err => {
alert('生成失败: ' + err);
})
.finally(() => {
btn.disabled = false;
btn.innerText = '开始生成';
});
return false;
};
</script>
</body>
</html>