v1.5.0: 舆情驱动自动化与持仓跟踪智能体合并为「⏰ 自动化」导航入口(双选项卡); 修复布尔设置存True/False导致读取失效的容错解析
This commit is contained in:
@@ -60,6 +60,11 @@ def page_tracking():
|
||||
return render_template("tracking.html", service=SERVICE_NAME, is_mock=IS_MOCK)
|
||||
|
||||
|
||||
@app.route("/automation")
|
||||
def page_automation():
|
||||
return render_template("automation.html", service=SERVICE_NAME, is_mock=IS_MOCK)
|
||||
|
||||
|
||||
@app.route("/admin")
|
||||
def page_admin():
|
||||
return render_template("admin.html", service=SERVICE_NAME, is_mock=IS_MOCK)
|
||||
@@ -577,30 +582,38 @@ def api_settings():
|
||||
|
||||
@app.route("/api/settings", methods=["POST"])
|
||||
def api_settings_save():
|
||||
from settings import save_all, set_setting
|
||||
from settings import save_all, set_setting, _bool_str
|
||||
body = request.get_json(silent=True) or {}
|
||||
mail = body.get("mail") or {}
|
||||
llm = body.get("llm") or {}
|
||||
mono = body.get("monitor") or {}
|
||||
# 邮件
|
||||
# 邮件(布尔规范化)
|
||||
for k in ("smtp_host", "smtp_port", "smtp_user", "smtp_pass", "smtp_mode",
|
||||
"email_to", "sender_name", "email_enabled"):
|
||||
"email_to", "sender_name"):
|
||||
if k in mail:
|
||||
set_setting(k, mail[k])
|
||||
if "email_enabled" in mail:
|
||||
set_setting("email_enabled", _bool_str(mail["email_enabled"]))
|
||||
# LLM
|
||||
for k in ("llm_base_url", "llm_api_key", "llm_model"):
|
||||
if k in llm:
|
||||
set_setting(k, str(llm[k]).strip())
|
||||
# 监控
|
||||
for k in ("monitor_enabled", "monitor_interval", "monitor_categories",
|
||||
for k in ("monitor_interval", "monitor_categories",
|
||||
"monitor_sentiment", "monitor_importance", "monitor_keywords"):
|
||||
if k in mono:
|
||||
set_setting(k, mono[k])
|
||||
if "monitor_enabled" in mono:
|
||||
set_setting("monitor_enabled", _bool_str(mono["monitor_enabled"]))
|
||||
# 持仓跟踪
|
||||
track = body.get("tracking") or {}
|
||||
for k in ("tracking_enabled", "tracking_interval", "tracking_notify", "tracking_impact_threshold"):
|
||||
for k in ("tracking_interval", "tracking_impact_threshold"):
|
||||
if k in track:
|
||||
set_setting(k, track[k])
|
||||
if "tracking_enabled" in track:
|
||||
set_setting("tracking_enabled", _bool_str(track["tracking_enabled"]))
|
||||
if "tracking_notify" in track:
|
||||
set_setting("tracking_notify", _bool_str(track["tracking_notify"]))
|
||||
return jsonify({"ok": True, "msg": "设置已保存"})
|
||||
|
||||
|
||||
|
||||
+16
-4
@@ -9,6 +9,18 @@ from config import LLM_BASE_URL, LLM_API_KEY, LLM_MODEL, LLM_MAX_TOKENS, \
|
||||
LLM_TEMPERATURE, LLM_TIMEOUT, MAIL_DEFAULTS, MONITOR_DEFAULTS, TRACKING_DEFAULTS
|
||||
|
||||
|
||||
def _truthy(v):
|
||||
"""容错布尔解析:兼容 1/0/True/False/true/false/on/yes"""
|
||||
if isinstance(v, bool):
|
||||
return v
|
||||
s = str(v or "").strip().lower()
|
||||
return s in ("1", "true", "yes", "on", "y")
|
||||
|
||||
|
||||
def _bool_str(v):
|
||||
return "1" if _truthy(v) else "0"
|
||||
|
||||
|
||||
def get_setting(key, default=""):
|
||||
r = query_one("SELECT value FROM settings WHERE key=?", (key,))
|
||||
return r["value"] if r else default
|
||||
@@ -52,7 +64,7 @@ def mail_config():
|
||||
"smtp_mode": get_setting("smtp_mode", MAIL_DEFAULTS["smtp_mode"]),
|
||||
"email_to": get_setting("email_to", MAIL_DEFAULTS["email_to"]),
|
||||
"sender_name": get_setting("sender_name", MAIL_DEFAULTS["sender_name"]),
|
||||
"email_enabled": get_setting("email_enabled", "1") == "1",
|
||||
"email_enabled": _truthy(get_setting("email_enabled", "1")),
|
||||
}
|
||||
|
||||
|
||||
@@ -67,7 +79,7 @@ def monitor_config():
|
||||
if k and k not in kw_list:
|
||||
kw_list.append(k)
|
||||
return {
|
||||
"enabled": get_setting("monitor_enabled", "1") == "1",
|
||||
"enabled": _truthy(get_setting("monitor_enabled", "1")),
|
||||
"interval_min": max(5, int(get_setting("monitor_interval", MONITOR_DEFAULTS["monitor_interval"]))),
|
||||
"categories": [c for c in cats.split(",") if c] if cats else [],
|
||||
"sentiment_weight": float(get_setting("monitor_sentiment", MONITOR_DEFAULTS["monitor_sentiment"])),
|
||||
@@ -97,9 +109,9 @@ def set_monitor_state(last_news_id=None, last_scan=None, last_sent=None):
|
||||
# ===================================================================== 持仓跟踪
|
||||
def tracking_config():
|
||||
return {
|
||||
"enabled": get_setting("tracking_enabled", TRACKING_DEFAULTS["tracking_enabled"]) == "1",
|
||||
"enabled": _truthy(get_setting("tracking_enabled", TRACKING_DEFAULTS["tracking_enabled"])),
|
||||
"interval_min": max(15, int(get_setting("tracking_interval", TRACKING_DEFAULTS["tracking_interval"]))),
|
||||
"notify": get_setting("tracking_notify", TRACKING_DEFAULTS["tracking_notify"]) == "1",
|
||||
"notify": _truthy(get_setting("tracking_notify", TRACKING_DEFAULTS["tracking_notify"])),
|
||||
"impact_threshold": float(get_setting("tracking_impact_threshold", TRACKING_DEFAULTS["tracking_impact_threshold"])),
|
||||
}
|
||||
|
||||
|
||||
@@ -202,6 +202,16 @@ tr:hover td { background: rgba(59,130,246,.05); }
|
||||
.between { justify-content: space-between; }
|
||||
.wrap { flex-wrap: wrap; }
|
||||
|
||||
/* ===== 选项卡 ===== */
|
||||
.tabs { display: flex; gap: 6px; border-bottom: 1px solid var(--border); margin-bottom: 4px; }
|
||||
.tab-item {
|
||||
padding: 10px 18px; border: none; background: none; color: var(--text2);
|
||||
font-size: 14px; font-weight: 600; cursor: pointer; border-bottom: 2px solid transparent;
|
||||
transition: .15s;
|
||||
}
|
||||
.tab-item:hover { color: var(--text); }
|
||||
.tab-item.active { color: var(--accent); border-bottom-color: var(--accent); background: none; }
|
||||
|
||||
/* ===== 设置页 ===== */
|
||||
.settings-form .sf-row { display: flex; align-items: center; gap: 12px; margin-bottom: 12px; }
|
||||
.settings-form .sf-row label { width: 110px; color: var(--text2); font-size: 13px; flex-shrink: 0; }
|
||||
|
||||
@@ -0,0 +1,179 @@
|
||||
/* 自动化监控页:舆情驱动 + 持仓跟踪 双选项卡 */
|
||||
let autoSettings = null;
|
||||
|
||||
/* ===================== 选项卡 ===================== */
|
||||
$$('.tab-item').forEach(t => t.onclick = () => {
|
||||
$$('.tab-item').forEach(x => x.classList.toggle('active', x === t));
|
||||
$$('.tab-panel').forEach(p => p.style.display = (p.id === 'panel-' + t.dataset.tab) ? '' : 'none');
|
||||
});
|
||||
|
||||
/* ===================== 加载 ===================== */
|
||||
async function loadAuto() {
|
||||
try {
|
||||
autoSettings = await api('/api/settings');
|
||||
const mono = autoSettings.monitor, tr = autoSettings.tracking;
|
||||
// 舆情
|
||||
$('#monitorEnabled').checked = !!mono.enabled;
|
||||
$('#monitorInterval').value = mono.interval_min;
|
||||
$('#monitorImportance').value = mono.importance_threshold;
|
||||
$('#monitorSentiment').value = mono.sentiment_weight;
|
||||
$('#monitorKeywords').value = (mono.keywords || []).join(',');
|
||||
const cats = mono.categories || [];
|
||||
$$('#monitorCats input').forEach(c => c.checked = cats.includes(c.value));
|
||||
renderMonitorState(autoSettings.state);
|
||||
// 持仓跟踪
|
||||
$('#trackingEnabled').checked = !!tr.enabled;
|
||||
$('#trackingInterval').value = tr.interval_min;
|
||||
$('#trackingImpact').value = tr.impact_threshold;
|
||||
$('#trackingNotify').checked = !!tr.notify;
|
||||
renderTrackingState(tr);
|
||||
loadLog();
|
||||
loadTrackReports();
|
||||
} catch (e) { toast('加载失败'); }
|
||||
}
|
||||
|
||||
function renderMonitorState(state) {
|
||||
$('#monitorState').innerHTML = `
|
||||
<div class="mini-stat"><div class="ms-label">已处理新闻水位</div><div class="ms-value num">#${state.last_news_id || 0}</div></div>
|
||||
<div class="mini-stat"><div class="ms-label">上次扫描</div><div class="ms-value">${state.last_scan || '—'}</div></div>
|
||||
<div class="mini-stat"><div class="ms-label">上次通知</div><div class="ms-value num">${state.last_sent_count || 0} 条</div></div>`;
|
||||
}
|
||||
|
||||
function renderTrackingState(tr) {
|
||||
$('#trackingState').innerHTML = `
|
||||
<div class="mini-stat"><div class="ms-label">自动跟踪</div><div class="ms-value">${tr.enabled ? '✅ 开启' : '⏸ 关闭'}(每 ${tr.interval_min} 分钟)</div></div>
|
||||
<div class="mini-stat"><div class="ms-label">上次运行</div><div class="ms-value">${tr.last_run || '—'}</div></div>
|
||||
<div class="mini-stat"><div class="ms-label">最近跟踪</div><div class="ms-value">${tr.last_stock || '—'}</div></div>
|
||||
<div class="mini-stat"><div class="ms-label">最近告警影响度</div><div class="ms-value num">${tr.last_alert ? tr.last_alert + '/100' : '—'}</div></div>`;
|
||||
}
|
||||
|
||||
function collectMonitor() {
|
||||
const cats = $$('#monitorCats input:checked').map(c => c.value);
|
||||
return {
|
||||
monitor_enabled: $('#monitorEnabled').checked,
|
||||
monitor_interval: $('#monitorInterval').value,
|
||||
monitor_importance: $('#monitorImportance').value,
|
||||
monitor_sentiment: $('#monitorSentiment').value,
|
||||
monitor_categories: cats.join(','),
|
||||
monitor_keywords: $('#monitorKeywords').value
|
||||
};
|
||||
}
|
||||
|
||||
/* ===================== 保存 ===================== */
|
||||
async function saveMonitor() {
|
||||
try {
|
||||
const r = await api('/api/settings', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ monitor: collectMonitor() }) });
|
||||
toast(r.msg || '已保存');
|
||||
} catch (e) { toast('保存失败:' + e.message); }
|
||||
}
|
||||
|
||||
async function saveTracking() {
|
||||
try {
|
||||
const r = await api('/api/settings', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ tracking: {
|
||||
tracking_enabled: $('#trackingEnabled').checked,
|
||||
tracking_interval: $('#trackingInterval').value,
|
||||
tracking_impact_threshold: $('#trackingImpact').value,
|
||||
tracking_notify: $('#trackingNotify').checked
|
||||
} }) });
|
||||
toast(r.msg || '已保存');
|
||||
} catch (e) { toast('保存失败:' + e.message); }
|
||||
}
|
||||
|
||||
/* ===================== 动作 ===================== */
|
||||
async function scanNow() {
|
||||
const btn = event.target; btn.disabled = true;
|
||||
try {
|
||||
const r = await api('/api/monitor/scan', { method: 'POST' });
|
||||
toast(`扫描完成:检查 ${r.checked} 条,重要 ${r.important} 条,发送 ${r.sent} 条`);
|
||||
loadAuto();
|
||||
} catch (e) { toast('扫描失败:' + e.message); }
|
||||
btn.disabled = false;
|
||||
}
|
||||
|
||||
async function runTrackAll() {
|
||||
const btn = event.target; btn.disabled = true;
|
||||
try {
|
||||
const r = await api('/api/tracking/run', { method: 'POST' });
|
||||
toast(r.msg || '已启动');
|
||||
setTimeout(loadTrackReports, 5000);
|
||||
setTimeout(loadTrackReports, 30000);
|
||||
setTimeout(loadTrackReports, 70000);
|
||||
} catch (e) { toast('启动失败:' + e.message); }
|
||||
btn.disabled = false;
|
||||
}
|
||||
|
||||
/* ===================== 日志与报告 ===================== */
|
||||
async function loadLog() {
|
||||
try {
|
||||
const d = await api('/api/monitor/log');
|
||||
const items = d.items || [];
|
||||
if (!items.length) return;
|
||||
$('#logTb').innerHTML = items.map(n => `
|
||||
<tr>
|
||||
<td style="color:var(--muted)">${n.sent_at}</td>
|
||||
<td style="max-width:300px;white-space:normal">${escapeHtml(n.title)}</td>
|
||||
<td><span class="cat-tag ${n.category}">${n.category}</span></td>
|
||||
<td class="num ${pctClass(n.sentiment)}">${n.sentiment > 0 ? '利好' : n.sentiment < 0 ? '利空' : '中性'} (${Number(n.sentiment).toFixed(2)})</td>
|
||||
<td class="num"><b>${n.importance}</b></td>
|
||||
<td><span class="tag ${n.status === 'sent' ? 'tag-推荐' : n.status === 'skipped' ? 'tag-关注' : 'tag-负'}">${n.status}</span></td>
|
||||
</tr>`).join('');
|
||||
} catch (e) {}
|
||||
}
|
||||
|
||||
async function loadTrackReports() {
|
||||
try {
|
||||
const d = await api('/api/tracking');
|
||||
const items = d.items || [];
|
||||
if (!items.length) return;
|
||||
$('#trackTb').innerHTML = items.slice(0, 10).map(r => {
|
||||
let meta = {};
|
||||
try { meta = JSON.parse(r.meta || '{}'); } catch (e) {}
|
||||
const sigCls = meta.impact_score >= 65 ? 'tag-强烈推荐' : meta.impact_score >= 45 ? 'tag-推荐' : 'tag-关注';
|
||||
return `<tr class="row-link" onclick="showTrack(${r.id})">
|
||||
<td style="color:var(--muted)">${r.created_at}</td>
|
||||
<td><b>${r.stock_name}</b><div style="color:var(--muted);font-size:12px">${r.code}</div></td>
|
||||
<td><span class="tag ${sigCls}">${meta.impact_score || '—'}</span></td>
|
||||
<td><span class="tag tag-${meta.change_kind === '利好' ? '强烈推荐' : meta.change_kind === '利空' ? '负' : '关注'}">${meta.change_kind || '—'}</span></td>
|
||||
<td style="max-width:320px;white-space:normal;color:var(--text2)">${escapeHtml(meta.summary || '')}</td>
|
||||
<td><button class="btn" onclick="event.stopPropagation();showTrack(${r.id})">查看 ↗</button></td>
|
||||
</tr>`;
|
||||
}).join('');
|
||||
} catch (e) {}
|
||||
}
|
||||
|
||||
async function showTrack(id) {
|
||||
try {
|
||||
const d = await api('/api/tracking/' + id);
|
||||
const meta = d.meta || {}, src = d.sources || {};
|
||||
const sigCls = meta.impact_score >= 65 ? 'tag-强烈推荐' : meta.impact_score >= 45 ? 'tag-推荐' : 'tag-关注';
|
||||
const newsBlock = (label, icon, seg) => {
|
||||
if (!seg) return '';
|
||||
const txt = ((seg.db || '') + (seg.rag || '')).trim();
|
||||
return `<details><summary>${icon} ${label} <span class="src-count">${txt.includes('[') ? txt.split('[').length - 1 : 0}</span></summary>
|
||||
<div class="src-body"><pre class="src-pre">${escapeHtml(txt || '暂无')}</pre></div></details>`;
|
||||
};
|
||||
openModal(`
|
||||
<h3>🧭 产业链跟踪:${d.stock_name}(${d.code})</h3>
|
||||
<div class="news-meta" style="margin-bottom:12px">
|
||||
<span class="cat-tag">${escapeHtml(d.industry)}</span>
|
||||
<span class="tag ${sigCls}">影响度 ${meta.impact_score || '—'}/100</span>
|
||||
<span class="tag tag-${meta.change_kind === '利好' ? '强烈推荐' : meta.change_kind === '利空' ? '负' : '关注'}">${meta.change_kind || '—'}</span>
|
||||
<span style="color:var(--muted)">${d.created_at}</span>
|
||||
</div>
|
||||
<div class="markdown-body">${mdRender(d.report)}</div>
|
||||
<div style="margin-top:16px;border-top:1px solid var(--border);padding-top:10px">
|
||||
<b style="color:var(--gold)">数据源(智能体参考内容)</b>
|
||||
${newsBlock('个股直接动态', '📄', src.direct)}
|
||||
${newsBlock('上游产业链(供给/成本)', '⬆️', src.upstream)}
|
||||
${newsBlock('下游产业链(需求/景气)', '⬇️', src.downstream)}
|
||||
${newsBlock('同业竞争', '🏢', src.peers)}
|
||||
</div>
|
||||
<div style="margin-top:14px;text-align:right">
|
||||
<button class="btn" onclick="document.querySelector('.modal-mask').classList.remove('show')">关闭</button>
|
||||
<a class="btn btn-primary" href="/stock/${d.code}" target="_blank">查看个股 →</a>
|
||||
</div>
|
||||
`);
|
||||
} catch (e) { toast('加载失败'); }
|
||||
}
|
||||
|
||||
loadAuto();
|
||||
+2
-79
@@ -1,10 +1,10 @@
|
||||
/* 系统设置页 */
|
||||
/* 系统设置页:邮件通知 + 大模型接口(舆情/跟踪已移至 /automation) */
|
||||
let curSettings = null;
|
||||
|
||||
async function loadSettings() {
|
||||
try {
|
||||
curSettings = await api('/api/settings');
|
||||
const mail = curSettings.mail, mono = curSettings.monitor, llm = curSettings.llm;
|
||||
const mail = curSettings.mail, llm = curSettings.llm;
|
||||
$('#emailEnabled').checked = !!mail.email_enabled;
|
||||
$('#smtpHost').value = mail.smtp_host;
|
||||
$('#smtpPort').value = mail.smtp_port;
|
||||
@@ -16,42 +16,11 @@ async function loadSettings() {
|
||||
$('#llmBaseUrl').value = llm.base_url;
|
||||
$('#llmApiKey').value = llm.api_key;
|
||||
$('#llmModel').value = llm.model;
|
||||
$('#monitorEnabled').checked = !!mono.enabled;
|
||||
$('#monitorInterval').value = mono.interval_min;
|
||||
$('#monitorImportance').value = mono.importance_threshold;
|
||||
$('#monitorSentiment').value = mono.sentiment_weight;
|
||||
$('#monitorKeywords').value = (mono.keywords || []).join(',');
|
||||
// 分类勾选
|
||||
const cats = mono.categories || [];
|
||||
$$('#monitorCats input').forEach(c => c.checked = cats.includes(c.value));
|
||||
renderState(curSettings.state);
|
||||
// 持仓跟踪
|
||||
const tr = curSettings.tracking || {};
|
||||
$('#trackingEnabled').checked = !!tr.enabled;
|
||||
$('#trackingInterval').value = tr.interval_min;
|
||||
$('#trackingImpact').value = tr.impact_threshold;
|
||||
$('#trackingNotify').checked = !!tr.notify;
|
||||
renderTrackingState(tr);
|
||||
loadLog();
|
||||
} catch (e) {
|
||||
toast('设置加载失败');
|
||||
}
|
||||
}
|
||||
|
||||
function renderState(state) {
|
||||
$('#monitorState').innerHTML = `
|
||||
<div class="mini-stat"><div class="ms-label">已处理新闻水位</div><div class="ms-value num">#${state.last_news_id || 0}</div></div>
|
||||
<div class="mini-stat"><div class="ms-label">上次扫描</div><div class="ms-value">${state.last_scan || '—'}</div></div>
|
||||
<div class="mini-stat"><div class="ms-label">上次通知</div><div class="ms-value num">${state.last_sent_count || 0} 条</div></div>`;
|
||||
}
|
||||
|
||||
function renderTrackingState(tr) {
|
||||
$('#trackingState').innerHTML = `
|
||||
<div class="mini-stat"><div class="ms-label">上次运行</div><div class="ms-value">${tr.last_run || '—'}</div></div>
|
||||
<div class="mini-stat"><div class="ms-label">最近跟踪</div><div class="ms-value">${tr.last_stock || '—'}</div></div>
|
||||
<div class="mini-stat"><div class="ms-label">最近告警影响度</div><div class="ms-value num">${tr.last_alert ? tr.last_alert + '/100' : '—'}</div></div>`;
|
||||
}
|
||||
|
||||
function collectMail() {
|
||||
return {
|
||||
smtp_host: $('#smtpHost').value.trim(), smtp_port: $('#smtpPort').value,
|
||||
@@ -61,18 +30,6 @@ function collectMail() {
|
||||
};
|
||||
}
|
||||
|
||||
function collectMonitor() {
|
||||
const cats = $$('#monitorCats input:checked').map(c => c.value);
|
||||
return {
|
||||
monitor_enabled: $('#monitorEnabled').checked,
|
||||
monitor_interval: $('#monitorInterval').value,
|
||||
monitor_importance: $('#monitorImportance').value,
|
||||
monitor_sentiment: $('#monitorSentiment').value,
|
||||
monitor_categories: cats.join(','),
|
||||
monitor_keywords: $('#monitorKeywords').value
|
||||
};
|
||||
}
|
||||
|
||||
async function save(which) {
|
||||
try {
|
||||
const body = {};
|
||||
@@ -82,13 +39,6 @@ async function save(which) {
|
||||
llm_api_key: $('#llmApiKey').value.trim(),
|
||||
llm_model: $('#llmModel').value.trim()
|
||||
};
|
||||
if (which === 'monitor') body.monitor = collectMonitor();
|
||||
if (which === 'tracking') body.tracking = {
|
||||
tracking_enabled: $('#trackingEnabled').checked,
|
||||
tracking_interval: $('#trackingInterval').value,
|
||||
tracking_impact_threshold: $('#trackingImpact').value,
|
||||
tracking_notify: $('#trackingNotify').checked
|
||||
};
|
||||
const r = await api('/api/settings', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(body) });
|
||||
toast(r.msg || '已保存');
|
||||
} catch (e) { toast('保存失败:' + e.message); }
|
||||
@@ -116,31 +66,4 @@ async function testLlm() {
|
||||
btn.disabled = false; btn.textContent = '🔌 测试连接';
|
||||
}
|
||||
|
||||
async function scanNow() {
|
||||
const btn = event.target; btn.disabled = true;
|
||||
try {
|
||||
const r = await api('/api/monitor/scan', { method: 'POST' });
|
||||
toast(`扫描完成:检查 ${r.checked} 条,重要 ${r.important} 条,发送 ${r.sent} 条` + (r.error ? '(' + r.error + ')' : ''));
|
||||
loadSettings();
|
||||
} catch (e) { toast('扫描失败:' + e.message); }
|
||||
btn.disabled = false;
|
||||
}
|
||||
|
||||
async function loadLog() {
|
||||
try {
|
||||
const d = await api('/api/monitor/log');
|
||||
const items = d.items || [];
|
||||
if (!items.length) return;
|
||||
$('#logTb').innerHTML = items.map(n => `
|
||||
<tr>
|
||||
<td style="color:var(--muted)">${n.sent_at}</td>
|
||||
<td style="max-width:300px;white-space:normal">${escapeHtml(n.title)}</td>
|
||||
<td><span class="cat-tag ${n.category}">${n.category}</span></td>
|
||||
<td class="num ${pctClass(n.sentiment)}">${n.sentiment > 0 ? '利好' : n.sentiment < 0 ? '利空' : '中性'} (${Number(n.sentiment).toFixed(2)})</td>
|
||||
<td class="num"><b>${n.importance}</b></td>
|
||||
<td><span class="tag ${n.status === 'sent' ? 'tag-推荐' : n.status === 'skipped' ? 'tag-关注' : 'tag-负'}">${n.status}</span></td>
|
||||
</tr>`).join('');
|
||||
} catch (e) {}
|
||||
}
|
||||
|
||||
loadSettings();
|
||||
@@ -0,0 +1,81 @@
|
||||
{% extends "base.html" %}
|
||||
{% block title %}自动化{% endblock %}
|
||||
{% block page_title %}自动化监控{% endblock %}
|
||||
{% block content %}
|
||||
<div class="card">
|
||||
<div class="tabs">
|
||||
<button class="tab-item active" data-tab="monitor">📰 舆情驱动自动化</button>
|
||||
<button class="tab-item" data-tab="tracking">🧭 持仓跟踪智能体</button>
|
||||
</div>
|
||||
|
||||
<!-- ======================= 舆情驱动 ======================= -->
|
||||
<div class="tab-panel" id="panel-monitor">
|
||||
<div class="between mt16">
|
||||
<div class="card-title" style="margin-bottom:0"><span class="bar" style="background:var(--cyan)"></span>定期扫描最新新闻,重要信息邮件通知</div>
|
||||
<label class="switch"><input type="checkbox" id="monitorEnabled"><span></span></label>
|
||||
</div>
|
||||
<div class="mt8 settings-form">
|
||||
<div class="sf-row"><label>扫描间隔(分钟)</label><input class="input" id="monitorInterval" type="number" min="5" style="width:120px"></div>
|
||||
<div class="sf-row"><label>重要度阈值 (0-100)</label><input class="input" id="monitorImportance" type="number" min="0" max="100" style="width:120px"></div>
|
||||
<div class="sf-row"><label>情感权重</label><input class="input" id="monitorSentiment" type="number" step="0.05" style="width:120px"></div>
|
||||
<div class="sf-row" style="align-items:flex-start"><label>监控分类</label>
|
||||
<div class="chk-group" id="monitorCats">
|
||||
<label><input type="checkbox" value="公司"> 公司</label>
|
||||
<label><input type="checkbox" value="业绩"> 业绩</label>
|
||||
<label><input type="checkbox" value="机构观点"> 机构观点</label>
|
||||
<label><input type="checkbox" value="行业"> 行业</label>
|
||||
<label><input type="checkbox" value="市场"> 市场</label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="sf-row" style="align-items:flex-start"><label>关键词(逗号分隔)</label><textarea class="input" id="monitorKeywords" rows="2" style="width:100%"></textarea></div>
|
||||
</div>
|
||||
<div class="flex mt16 wrap">
|
||||
<button class="btn btn-primary" onclick="saveMonitor()">💾 保存监控设置</button>
|
||||
<button class="btn" onclick="scanNow()">⚡ 立即扫描</button>
|
||||
</div>
|
||||
<div class="mt16 mini-stats" id="monitorState"></div>
|
||||
<div class="mt16" style="border-top:1px solid var(--border);padding-top:6px">
|
||||
<div class="card-title mt16" style="font-size:14px"><span class="bar" style="background:var(--cyan)"></span>通知日志</div>
|
||||
<div style="overflow:auto;max-height:340px">
|
||||
<table>
|
||||
<thead><tr><th>时间</th><th>标题</th><th>分类</th><th>情感</th><th>重要度</th><th>状态</th></tr></thead>
|
||||
<tbody id="logTb"><tr><td colspan="6" class="empty">暂无记录</td></tr></tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- ======================= 持仓跟踪 ======================= -->
|
||||
<div class="tab-panel" id="panel-tracking" style="display:none">
|
||||
<div class="between mt16">
|
||||
<div class="card-title" style="margin-bottom:0"><span class="bar" style="background:#8b5cf6"></span>AI 智能体定期跟踪持仓,深度分析产业链上下游</div>
|
||||
<label class="switch"><input type="checkbox" id="trackingEnabled"><span></span></label>
|
||||
</div>
|
||||
<div class="mt8 settings-form">
|
||||
<div class="sf-row"><label>跟踪间隔(分钟)</label><input class="input" id="trackingInterval" type="number" min="15" style="width:120px"></div>
|
||||
<div class="sf-row"><label>重大判定阈值</label><input class="input" id="trackingImpact" type="number" min="0" max="100" style="width:120px"></div>
|
||||
<div class="sf-row"><label>重大变化邮件通知</label>
|
||||
<label class="switch"><input type="checkbox" id="trackingNotify"><span></span></label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex mt16 wrap">
|
||||
<button class="btn btn-primary" onclick="saveTracking()">💾 保存跟踪设置</button>
|
||||
<button class="btn" onclick="runTrackAll()">⚡ 立即跟踪全部持仓</button>
|
||||
<button class="btn" onclick="location.href='/tracking'">🧭 查看全部跟踪报告</button>
|
||||
</div>
|
||||
<div class="mt16 mini-stats" id="trackingState"></div>
|
||||
<div class="mt16" style="border-top:1px solid var(--border);padding-top:6px">
|
||||
<div class="card-title mt16" style="font-size:14px"><span class="bar" style="background:#8b5cf6"></span>最近跟踪报告</div>
|
||||
<div style="overflow:auto;max-height:340px">
|
||||
<table>
|
||||
<thead><tr><th>时间</th><th>股票</th><th>影响度</th><th>性质</th><th>摘要</th><th>操作</th></tr></thead>
|
||||
<tbody id="trackTb"><tr><td colspan="6" class="empty">暂无跟踪记录</td></tr></tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
{% block scripts %}
|
||||
<script src="{{ url_for('static', filename='js/automation.js') }}"></script>
|
||||
{% endblock %}
|
||||
+1
-1
@@ -30,7 +30,7 @@
|
||||
<a href="/" class="nav-item" data-nav="/">📊 仪表盘</a>
|
||||
<a href="/stocks" class="nav-item" data-nav="/stocks">🏢 股票池</a>
|
||||
<a href="/recommend" class="nav-item" data-nav="/recommend">🎯 荐股中心</a>
|
||||
<a href="/tracking" class="nav-item" data-nav="/tracking">🧭 持仓跟踪</a>
|
||||
<a href="/automation" class="nav-item" data-nav="/automation">⏰ 自动化</a>
|
||||
<a href="/strategies" class="nav-item" data-nav="/strategies">📈 量化策略</a>
|
||||
<a href="/news" class="nav-item" data-nav="/news">📰 财经新闻</a>
|
||||
<a href="/institutions" class="nav-item" data-nav="/institutions">🏦 机构动向</a>
|
||||
|
||||
+2
-60
@@ -43,69 +43,11 @@
|
||||
<button class="btn" onclick="testLlm()">🔌 测试连接</button>
|
||||
</div>
|
||||
<div class="mt16" style="border-top:1px solid var(--border);padding-top:12px;color:var(--muted);font-size:12px;line-height:1.8">
|
||||
用于 AI 深度研报生成。支持任意 OpenAI 兼容接口(DeepSeek / 火山方舟等)。修改后立即生效。
|
||||
用于 AI 深度研报生成。支持任意 OpenAI 兼容接口(DeepSeek / 火山方舟等)。修改后立即生效。<br>
|
||||
<span style="color:var(--gold)">💡 舆情监控 / 持仓跟踪等自动化参数已移至「⏰ 自动化」页面。</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 舆情监控 -->
|
||||
<div class="card mt16">
|
||||
<div class="between">
|
||||
<div class="card-title" style="margin-bottom:0"><span class="bar" style="background:var(--cyan)"></span>📰 舆情驱动自动化</div>
|
||||
<label class="switch"><input type="checkbox" id="monitorEnabled"><span></span></label>
|
||||
</div>
|
||||
<div class="mt8 settings-form">
|
||||
<div class="sf-row"><label>扫描间隔(分钟)</label><input class="input" id="monitorInterval" type="number" min="5" style="width:120px"></div>
|
||||
<div class="sf-row"><label>重要度阈值 (0-100)</label><input class="input" id="monitorImportance" type="number" min="0" max="100" style="width:120px"></div>
|
||||
<div class="sf-row"><label>情感权重</label><input class="input" id="monitorSentiment" type="number" step="0.05" style="width:120px"></div>
|
||||
<div class="sf-row" style="align-items:flex-start"><label>监控分类</label>
|
||||
<div class="chk-group" id="monitorCats">
|
||||
<label><input type="checkbox" value="公司"> 公司</label>
|
||||
<label><input type="checkbox" value="业绩"> 业绩</label>
|
||||
<label><input type="checkbox" value="机构观点"> 机构观点</label>
|
||||
<label><input type="checkbox" value="行业"> 行业</label>
|
||||
<label><input type="checkbox" value="市场"> 市场</label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="sf-row" style="align-items:flex-start"><label>关键词(逗号分隔)</label><textarea class="input" id="monitorKeywords" rows="2" style="width:100%"></textarea></div>
|
||||
</div>
|
||||
<div class="flex mt16 wrap">
|
||||
<button class="btn btn-primary" onclick="save('monitor')">💾 保存监控设置</button>
|
||||
<button class="btn" onclick="scanNow()">⚡ 立即扫描</button>
|
||||
</div>
|
||||
<div class="mt16 mini-stats" id="monitorState"></div>
|
||||
</div>
|
||||
|
||||
<!-- 持仓跟踪 -->
|
||||
<div class="card mt16">
|
||||
<div class="between">
|
||||
<div class="card-title" style="margin-bottom:0"><span class="bar" style="background:#8b5cf6"></span>🧭 持仓跟踪智能体</div>
|
||||
<label class="switch"><input type="checkbox" id="trackingEnabled"><span></span></label>
|
||||
</div>
|
||||
<div class="mt8 settings-form">
|
||||
<div class="sf-row"><label>跟踪间隔(分钟)</label><input class="input" id="trackingInterval" type="number" min="15" style="width:120px"></div>
|
||||
<div class="sf-row"><label>重大判定阈值</label><input class="input" id="trackingImpact" type="number" min="0" max="100" style="width:120px"></div>
|
||||
<div class="sf-row"><label>重大变化邮件通知</label>
|
||||
<label class="switch"><input type="checkbox" id="trackingNotify"><span></span></label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex mt16 wrap">
|
||||
<button class="btn btn-primary" onclick="save('tracking')">💾 保存跟踪设置</button>
|
||||
<button class="btn" onclick="location.href='/tracking'">🧭 打开持仓跟踪页</button>
|
||||
</div>
|
||||
<div class="mt16 mini-stats" id="trackingState"></div>
|
||||
</div>
|
||||
|
||||
<!-- 通知日志 -->
|
||||
<div class="card mt16">
|
||||
<div class="card-title"><span class="bar" style="background:var(--cyan)"></span>通知日志</div>
|
||||
<div style="overflow:auto;max-height:380px">
|
||||
<table>
|
||||
<thead><tr><th>时间</th><th>标题</th><th>分类</th><th>情感</th><th>重要度</th><th>状态</th></tr></thead>
|
||||
<tbody id="logTb"><tr><td colspan="6" class="empty">暂无记录</td></tr></tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
{% block scripts %}
|
||||
<script src="{{ url_for('static', filename='js/settings.js') }}"></script>
|
||||
|
||||
Reference in New Issue
Block a user