fix: AI review loading UX and Gate preview snapshot time filter SQL

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
dekun
2026-06-05 13:51:16 +08:00
parent 86a6081090
commit 4ac4c062e0
8 changed files with 264 additions and 36 deletions
+48 -8
View File
@@ -232,7 +232,7 @@
.stats-period-block h3{font-size:1rem;color:#dbe4ff;margin-bottom:4px} .stats-period-block h3{font-size:1rem;color:#dbe4ff;margin-bottom:4px}
.stats-period-block .sub{font-size:.78rem;color:#8892b0;margin-bottom:10px;line-height:1.4} .stats-period-block .sub{font-size:.78rem;color:#8892b0;margin-bottom:10px;line-height:1.4}
</style> </style>
<link rel="stylesheet" href="/static/instance_theme.css?v=6"> <link rel="stylesheet" href="/static/instance_theme.css?v=7">
</head> </head>
<body data-page="{{ page }}"> <body data-page="{{ page }}">
@@ -813,11 +813,11 @@
</div> </div>
<div class="form-row"> <div class="form-row">
<input type="date" id="day_date"> <input type="date" id="day_date">
<button type="button" onclick="genDaily()">生成日复盘</button> <button type="button" id="gen-daily-btn" onclick="genDaily()">生成日复盘</button>
<button type="button" onclick="exportDailyBundleMd()" style="background:#1f3a5a">导出当日日复盘MD</button> <button type="button" onclick="exportDailyBundleMd()" style="background:#1f3a5a">导出当日日复盘MD</button>
<input type="date" id="week_start"> <input type="date" id="week_start">
<input type="date" id="week_end"> <input type="date" id="week_end">
<button type="button" onclick="genWeekly()">生成周复盘</button> <button type="button" id="gen-weekly-btn" onclick="genWeekly()">生成周复盘</button>
<button type="button" onclick="exportWeeklyBundleMd()" style="background:#1f3a5a">导出当周复盘MD</button> <button type="button" onclick="exportWeeklyBundleMd()" style="background:#1f3a5a">导出当周复盘MD</button>
</div> </div>
<div class="ai-result-wrap" id="daily_result_wrap" style="display:none"> <div class="ai-result-wrap" id="daily_result_wrap" style="display:none">
@@ -902,7 +902,7 @@
</div> </div>
<script src="/static/instance_ui.js?v=1"></script> <script src="/static/instance_ui.js?v=1"></script>
<script src="/static/ai_review_render.js?v=1"></script> <script src="/static/ai_review_render.js?v=2"></script>
<script src="/static/form_submit_guard.js?v=2"></script> <script src="/static/form_submit_guard.js?v=2"></script>
<script> <script>
const JOURNAL_ENTRY_REASON_OPTIONS = {{ entry_reason_options | tojson }}; const JOURNAL_ENTRY_REASON_OPTIONS = {{ entry_reason_options | tojson }};
@@ -1233,36 +1233,76 @@ function loadReviews(){
} }
function genDaily(){ function genDaily(){
if(window.AiReviewRender && AiReviewRender.isGenerating && AiReviewRender.isGenerating()) return;
const d = document.getElementById("day_date").value; const d = document.getElementById("day_date").value;
if(!d){alert("请选择日期");return;} if(!d){alert("请选择日期");return;}
if(window.AiReviewRender && AiReviewRender.setGenerating){
AiReviewRender.setGenerating({
wrapId:"daily_result_wrap",
elId:"daily_result",
btnId:"gen-daily-btn",
message:"生成日复盘中,请稍候…(AI 分析可能需要 1~3 分钟)",
btnLabel:"日复盘生成中…"
});
}
fetch("/ai_daily_review",{method:"POST",headers:{"Content-Type":"application/x-www-form-urlencoded"},body:`date=${encodeURIComponent(d)}`}) fetch("/ai_daily_review",{method:"POST",headers:{"Content-Type":"application/x-www-form-urlencoded"},body:`date=${encodeURIComponent(d)}`})
.then(r=>{ if(!r.ok) throw new Error("HTTP "+r.status); return r.json(); }) .then(r=>{ if(!r.ok) throw new Error("HTTP "+r.status); return r.json(); })
.then(data=>{ .then(data=>{
if(!data || data.result == null) throw new Error("返回数据为空");
const el=document.getElementById("daily_result"); const el=document.getElementById("daily_result");
const wrap=document.getElementById("daily_result_wrap"); const wrap=document.getElementById("daily_result_wrap");
setAiReviewMarkdown(el, data.result); setAiReviewMarkdown(el, data.result);
if(wrap){ wrap.style.display="block"; } if(wrap){ wrap.style.display="block"; }
else { el.style.display="block"; } else if(el){ el.style.display="block"; }
if(window.AiReviewRender && AiReviewRender.clearGenerating) AiReviewRender.clearGenerating("gen-daily-btn");
loadReviews(); loadReviews();
}) })
.catch(e=>alert("生成日复盘失败:"+(e.message||e))); .catch(e=>{
if(window.AiReviewRender && AiReviewRender.clearGenerating) AiReviewRender.clearGenerating("gen-daily-btn");
const el=document.getElementById("daily_result");
if(el){
el.classList.remove("is-loading","ai-result-md");
el.innerText="生成失败,请重试。";
}
alert("生成日复盘失败:"+(e.message||e));
});
} }
function genWeekly(){ function genWeekly(){
if(window.AiReviewRender && AiReviewRender.isGenerating && AiReviewRender.isGenerating()) return;
const s=document.getElementById("week_start").value; const s=document.getElementById("week_start").value;
const e=document.getElementById("week_end").value; const e=document.getElementById("week_end").value;
if(!s || !e){alert("请选择起止日期");return;} if(!s || !e){alert("请选择起止日期");return;}
if(window.AiReviewRender && AiReviewRender.setGenerating){
AiReviewRender.setGenerating({
wrapId:"weekly_result_wrap",
elId:"weekly_result",
btnId:"gen-weekly-btn",
message:"生成周复盘中,请稍候…(AI 分析可能需要 1~3 分钟)",
btnLabel:"周复盘生成中…"
});
}
fetch("/ai_weekly_review",{method:"POST",headers:{"Content-Type":"application/x-www-form-urlencoded"},body:`start_date=${encodeURIComponent(s)}&end_date=${encodeURIComponent(e)}`}) fetch("/ai_weekly_review",{method:"POST",headers:{"Content-Type":"application/x-www-form-urlencoded"},body:`start_date=${encodeURIComponent(s)}&end_date=${encodeURIComponent(e)}`})
.then(r=>{ if(!r.ok) throw new Error("HTTP "+r.status); return r.json(); }) .then(r=>{ if(!r.ok) throw new Error("HTTP "+r.status); return r.json(); })
.then(data=>{ .then(data=>{
if(!data || data.result == null) throw new Error("返回数据为空");
const el=document.getElementById("weekly_result"); const el=document.getElementById("weekly_result");
const wrap=document.getElementById("weekly_result_wrap"); const wrap=document.getElementById("weekly_result_wrap");
setAiReviewMarkdown(el, data.result); setAiReviewMarkdown(el, data.result);
if(wrap){ wrap.style.display="block"; } if(wrap){ wrap.style.display="block"; }
else { el.style.display="block"; } else if(el){ el.style.display="block"; }
if(window.AiReviewRender && AiReviewRender.clearGenerating) AiReviewRender.clearGenerating("gen-weekly-btn");
loadReviews(); loadReviews();
}) })
.catch(e=>alert("生成周复盘失败:"+(e.message||e))); .catch(e=>{
if(window.AiReviewRender && AiReviewRender.clearGenerating) AiReviewRender.clearGenerating("gen-weekly-btn");
const el=document.getElementById("weekly_result");
if(el){
el.classList.remove("is-loading","ai-result-md");
el.innerText="生成失败,请重试。";
}
alert("生成周复盘失败:"+(e.message||e));
});
} }
function exportDailyBundleMd(){ function exportDailyBundleMd(){
+48 -8
View File
@@ -232,7 +232,7 @@
.stats-period-block h3{font-size:1rem;color:#dbe4ff;margin-bottom:4px} .stats-period-block h3{font-size:1rem;color:#dbe4ff;margin-bottom:4px}
.stats-period-block .sub{font-size:.78rem;color:#8892b0;margin-bottom:10px;line-height:1.4} .stats-period-block .sub{font-size:.78rem;color:#8892b0;margin-bottom:10px;line-height:1.4}
</style> </style>
<link rel="stylesheet" href="/static/instance_theme.css?v=6"> <link rel="stylesheet" href="/static/instance_theme.css?v=7">
</head> </head>
<body data-page="{{ page }}"> <body data-page="{{ page }}">
@@ -813,11 +813,11 @@
</div> </div>
<div class="form-row"> <div class="form-row">
<input type="date" id="day_date"> <input type="date" id="day_date">
<button type="button" onclick="genDaily()">生成日复盘</button> <button type="button" id="gen-daily-btn" onclick="genDaily()">生成日复盘</button>
<button type="button" onclick="exportDailyBundleMd()" style="background:#1f3a5a">导出当日日复盘MD</button> <button type="button" onclick="exportDailyBundleMd()" style="background:#1f3a5a">导出当日日复盘MD</button>
<input type="date" id="week_start"> <input type="date" id="week_start">
<input type="date" id="week_end"> <input type="date" id="week_end">
<button type="button" onclick="genWeekly()">生成周复盘</button> <button type="button" id="gen-weekly-btn" onclick="genWeekly()">生成周复盘</button>
<button type="button" onclick="exportWeeklyBundleMd()" style="background:#1f3a5a">导出当周复盘MD</button> <button type="button" onclick="exportWeeklyBundleMd()" style="background:#1f3a5a">导出当周复盘MD</button>
</div> </div>
<div class="ai-result-wrap" id="daily_result_wrap" style="display:none"> <div class="ai-result-wrap" id="daily_result_wrap" style="display:none">
@@ -902,7 +902,7 @@
</div> </div>
<script src="/static/instance_ui.js?v=1"></script> <script src="/static/instance_ui.js?v=1"></script>
<script src="/static/ai_review_render.js?v=1"></script> <script src="/static/ai_review_render.js?v=2"></script>
<script src="/static/form_submit_guard.js?v=2"></script> <script src="/static/form_submit_guard.js?v=2"></script>
<script> <script>
const JOURNAL_ENTRY_REASON_OPTIONS = {{ entry_reason_options | tojson }}; const JOURNAL_ENTRY_REASON_OPTIONS = {{ entry_reason_options | tojson }};
@@ -1233,36 +1233,76 @@ function loadReviews(){
} }
function genDaily(){ function genDaily(){
if(window.AiReviewRender && AiReviewRender.isGenerating && AiReviewRender.isGenerating()) return;
const d = document.getElementById("day_date").value; const d = document.getElementById("day_date").value;
if(!d){alert("请选择日期");return;} if(!d){alert("请选择日期");return;}
if(window.AiReviewRender && AiReviewRender.setGenerating){
AiReviewRender.setGenerating({
wrapId:"daily_result_wrap",
elId:"daily_result",
btnId:"gen-daily-btn",
message:"生成日复盘中,请稍候…(AI 分析可能需要 1~3 分钟)",
btnLabel:"日复盘生成中…"
});
}
fetch("/ai_daily_review",{method:"POST",headers:{"Content-Type":"application/x-www-form-urlencoded"},body:`date=${encodeURIComponent(d)}`}) fetch("/ai_daily_review",{method:"POST",headers:{"Content-Type":"application/x-www-form-urlencoded"},body:`date=${encodeURIComponent(d)}`})
.then(r=>{ if(!r.ok) throw new Error("HTTP "+r.status); return r.json(); }) .then(r=>{ if(!r.ok) throw new Error("HTTP "+r.status); return r.json(); })
.then(data=>{ .then(data=>{
if(!data || data.result == null) throw new Error("返回数据为空");
const el=document.getElementById("daily_result"); const el=document.getElementById("daily_result");
const wrap=document.getElementById("daily_result_wrap"); const wrap=document.getElementById("daily_result_wrap");
setAiReviewMarkdown(el, data.result); setAiReviewMarkdown(el, data.result);
if(wrap){ wrap.style.display="block"; } if(wrap){ wrap.style.display="block"; }
else { el.style.display="block"; } else if(el){ el.style.display="block"; }
if(window.AiReviewRender && AiReviewRender.clearGenerating) AiReviewRender.clearGenerating("gen-daily-btn");
loadReviews(); loadReviews();
}) })
.catch(e=>alert("生成日复盘失败:"+(e.message||e))); .catch(e=>{
if(window.AiReviewRender && AiReviewRender.clearGenerating) AiReviewRender.clearGenerating("gen-daily-btn");
const el=document.getElementById("daily_result");
if(el){
el.classList.remove("is-loading","ai-result-md");
el.innerText="生成失败,请重试。";
}
alert("生成日复盘失败:"+(e.message||e));
});
} }
function genWeekly(){ function genWeekly(){
if(window.AiReviewRender && AiReviewRender.isGenerating && AiReviewRender.isGenerating()) return;
const s=document.getElementById("week_start").value; const s=document.getElementById("week_start").value;
const e=document.getElementById("week_end").value; const e=document.getElementById("week_end").value;
if(!s || !e){alert("请选择起止日期");return;} if(!s || !e){alert("请选择起止日期");return;}
if(window.AiReviewRender && AiReviewRender.setGenerating){
AiReviewRender.setGenerating({
wrapId:"weekly_result_wrap",
elId:"weekly_result",
btnId:"gen-weekly-btn",
message:"生成周复盘中,请稍候…(AI 分析可能需要 1~3 分钟)",
btnLabel:"周复盘生成中…"
});
}
fetch("/ai_weekly_review",{method:"POST",headers:{"Content-Type":"application/x-www-form-urlencoded"},body:`start_date=${encodeURIComponent(s)}&end_date=${encodeURIComponent(e)}`}) fetch("/ai_weekly_review",{method:"POST",headers:{"Content-Type":"application/x-www-form-urlencoded"},body:`start_date=${encodeURIComponent(s)}&end_date=${encodeURIComponent(e)}`})
.then(r=>{ if(!r.ok) throw new Error("HTTP "+r.status); return r.json(); }) .then(r=>{ if(!r.ok) throw new Error("HTTP "+r.status); return r.json(); })
.then(data=>{ .then(data=>{
if(!data || data.result == null) throw new Error("返回数据为空");
const el=document.getElementById("weekly_result"); const el=document.getElementById("weekly_result");
const wrap=document.getElementById("weekly_result_wrap"); const wrap=document.getElementById("weekly_result_wrap");
setAiReviewMarkdown(el, data.result); setAiReviewMarkdown(el, data.result);
if(wrap){ wrap.style.display="block"; } if(wrap){ wrap.style.display="block"; }
else { el.style.display="block"; } else if(el){ el.style.display="block"; }
if(window.AiReviewRender && AiReviewRender.clearGenerating) AiReviewRender.clearGenerating("gen-weekly-btn");
loadReviews(); loadReviews();
}) })
.catch(e=>alert("生成周复盘失败:"+(e.message||e))); .catch(e=>{
if(window.AiReviewRender && AiReviewRender.clearGenerating) AiReviewRender.clearGenerating("gen-weekly-btn");
const el=document.getElementById("weekly_result");
if(el){
el.classList.remove("is-loading","ai-result-md");
el.innerText="生成失败,请重试。";
}
alert("生成周复盘失败:"+(e.message||e));
});
} }
function exportDailyBundleMd(){ function exportDailyBundleMd(){
+3 -2
View File
@@ -5573,9 +5573,10 @@ def render_main_page(page="trade"):
preview_snapshots = [] preview_snapshots = []
if page == "records": if page == "records":
try: try:
snap_ts = sql_list_time_field("preview_created_at", "snapshot_at")
snap_rows = conn.execute( snap_rows = conn.execute(
f"SELECT * FROM trend_pullback_preview_snapshots WHERE {sql_list_time_field('preview_created_at')} >= ? " f"SELECT * FROM trend_pullback_preview_snapshots WHERE {snap_ts} >= ? "
f"AND {sql_list_time_field('preview_created_at')} <= ? ORDER BY id DESC LIMIT 500", f"AND {snap_ts} <= ? ORDER BY id DESC LIMIT 500",
(start_bj, end_bj), (start_bj, end_bj),
).fetchall() ).fetchall()
for sr in snap_rows: for sr in snap_rows:
+48 -8
View File
@@ -269,7 +269,7 @@
.stats-split-row{grid-template-columns:1fr} .stats-split-row{grid-template-columns:1fr}
} }
</style> </style>
<link rel="stylesheet" href="/static/instance_theme.css?v=6"> <link rel="stylesheet" href="/static/instance_theme.css?v=7">
</head> </head>
<body data-page="{{ page }}"> <body data-page="{{ page }}">
@@ -769,11 +769,11 @@
</div> </div>
<div class="form-row"> <div class="form-row">
<input type="date" id="day_date"> <input type="date" id="day_date">
<button type="button" onclick="genDaily()">生成日复盘</button> <button type="button" id="gen-daily-btn" onclick="genDaily()">生成日复盘</button>
<button type="button" onclick="exportDailyBundleMd()" style="background:#1f3a5a">导出当日日复盘MD</button> <button type="button" onclick="exportDailyBundleMd()" style="background:#1f3a5a">导出当日日复盘MD</button>
<input type="date" id="week_start"> <input type="date" id="week_start">
<input type="date" id="week_end"> <input type="date" id="week_end">
<button type="button" onclick="genWeekly()">生成周复盘</button> <button type="button" id="gen-weekly-btn" onclick="genWeekly()">生成周复盘</button>
<button type="button" onclick="exportWeeklyBundleMd()" style="background:#1f3a5a">导出当周复盘MD</button> <button type="button" onclick="exportWeeklyBundleMd()" style="background:#1f3a5a">导出当周复盘MD</button>
</div> </div>
<div class="ai-result-wrap" id="daily_result_wrap" style="display:none"> <div class="ai-result-wrap" id="daily_result_wrap" style="display:none">
@@ -871,7 +871,7 @@
</div> </div>
<script src="/static/instance_ui.js?v=1"></script> <script src="/static/instance_ui.js?v=1"></script>
<script src="/static/ai_review_render.js?v=1"></script> <script src="/static/ai_review_render.js?v=2"></script>
<script src="/static/form_submit_guard.js?v=2"></script> <script src="/static/form_submit_guard.js?v=2"></script>
<script> <script>
const JOURNAL_ENTRY_REASON_OPTIONS = {{ entry_reason_options | tojson }}; const JOURNAL_ENTRY_REASON_OPTIONS = {{ entry_reason_options | tojson }};
@@ -1196,36 +1196,76 @@ function loadReviews(){
} }
function genDaily(){ function genDaily(){
if(window.AiReviewRender && AiReviewRender.isGenerating && AiReviewRender.isGenerating()) return;
const d = document.getElementById("day_date").value; const d = document.getElementById("day_date").value;
if(!d){alert("请选择日期");return;} if(!d){alert("请选择日期");return;}
if(window.AiReviewRender && AiReviewRender.setGenerating){
AiReviewRender.setGenerating({
wrapId:"daily_result_wrap",
elId:"daily_result",
btnId:"gen-daily-btn",
message:"生成日复盘中,请稍候…(AI 分析可能需要 1~3 分钟)",
btnLabel:"日复盘生成中…"
});
}
fetch("/ai_daily_review",{method:"POST",headers:{"Content-Type":"application/x-www-form-urlencoded"},body:`date=${encodeURIComponent(d)}`}) fetch("/ai_daily_review",{method:"POST",headers:{"Content-Type":"application/x-www-form-urlencoded"},body:`date=${encodeURIComponent(d)}`})
.then(r=>{ if(!r.ok) throw new Error("HTTP "+r.status); return r.json(); }) .then(r=>{ if(!r.ok) throw new Error("HTTP "+r.status); return r.json(); })
.then(data=>{ .then(data=>{
if(!data || data.result == null) throw new Error("返回数据为空");
const el=document.getElementById("daily_result"); const el=document.getElementById("daily_result");
const wrap=document.getElementById("daily_result_wrap"); const wrap=document.getElementById("daily_result_wrap");
setAiReviewMarkdown(el, data.result); setAiReviewMarkdown(el, data.result);
if(wrap){ wrap.style.display="block"; } if(wrap){ wrap.style.display="block"; }
else { el.style.display="block"; } else if(el){ el.style.display="block"; }
if(window.AiReviewRender && AiReviewRender.clearGenerating) AiReviewRender.clearGenerating("gen-daily-btn");
loadReviews(); loadReviews();
}) })
.catch(e=>alert("生成日复盘失败:"+(e.message||e))); .catch(e=>{
if(window.AiReviewRender && AiReviewRender.clearGenerating) AiReviewRender.clearGenerating("gen-daily-btn");
const el=document.getElementById("daily_result");
if(el){
el.classList.remove("is-loading","ai-result-md");
el.innerText="生成失败,请重试。";
}
alert("生成日复盘失败:"+(e.message||e));
});
} }
function genWeekly(){ function genWeekly(){
if(window.AiReviewRender && AiReviewRender.isGenerating && AiReviewRender.isGenerating()) return;
const s=document.getElementById("week_start").value; const s=document.getElementById("week_start").value;
const e=document.getElementById("week_end").value; const e=document.getElementById("week_end").value;
if(!s || !e){alert("请选择起止日期");return;} if(!s || !e){alert("请选择起止日期");return;}
if(window.AiReviewRender && AiReviewRender.setGenerating){
AiReviewRender.setGenerating({
wrapId:"weekly_result_wrap",
elId:"weekly_result",
btnId:"gen-weekly-btn",
message:"生成周复盘中,请稍候…(AI 分析可能需要 1~3 分钟)",
btnLabel:"周复盘生成中…"
});
}
fetch("/ai_weekly_review",{method:"POST",headers:{"Content-Type":"application/x-www-form-urlencoded"},body:`start_date=${encodeURIComponent(s)}&end_date=${encodeURIComponent(e)}`}) fetch("/ai_weekly_review",{method:"POST",headers:{"Content-Type":"application/x-www-form-urlencoded"},body:`start_date=${encodeURIComponent(s)}&end_date=${encodeURIComponent(e)}`})
.then(r=>{ if(!r.ok) throw new Error("HTTP "+r.status); return r.json(); }) .then(r=>{ if(!r.ok) throw new Error("HTTP "+r.status); return r.json(); })
.then(data=>{ .then(data=>{
if(!data || data.result == null) throw new Error("返回数据为空");
const el=document.getElementById("weekly_result"); const el=document.getElementById("weekly_result");
const wrap=document.getElementById("weekly_result_wrap"); const wrap=document.getElementById("weekly_result_wrap");
setAiReviewMarkdown(el, data.result); setAiReviewMarkdown(el, data.result);
if(wrap){ wrap.style.display="block"; } if(wrap){ wrap.style.display="block"; }
else { el.style.display="block"; } else if(el){ el.style.display="block"; }
if(window.AiReviewRender && AiReviewRender.clearGenerating) AiReviewRender.clearGenerating("gen-weekly-btn");
loadReviews(); loadReviews();
}) })
.catch(e=>alert("生成周复盘失败:"+(e.message||e))); .catch(e=>{
if(window.AiReviewRender && AiReviewRender.clearGenerating) AiReviewRender.clearGenerating("gen-weekly-btn");
const el=document.getElementById("weekly_result");
if(el){
el.classList.remove("is-loading","ai-result-md");
el.innerText="生成失败,请重试。";
}
alert("生成周复盘失败:"+(e.message||e));
});
} }
function exportDailyBundleMd(){ function exportDailyBundleMd(){
+48 -8
View File
@@ -232,7 +232,7 @@
.stats-period-block h3{font-size:1rem;color:#dbe4ff;margin-bottom:4px} .stats-period-block h3{font-size:1rem;color:#dbe4ff;margin-bottom:4px}
.stats-period-block .sub{font-size:.78rem;color:#8892b0;margin-bottom:10px;line-height:1.4} .stats-period-block .sub{font-size:.78rem;color:#8892b0;margin-bottom:10px;line-height:1.4}
</style> </style>
<link rel="stylesheet" href="/static/instance_theme.css?v=6"> <link rel="stylesheet" href="/static/instance_theme.css?v=7">
</head> </head>
<body data-page="{{ page }}"> <body data-page="{{ page }}">
@@ -822,11 +822,11 @@
</div> </div>
<div class="form-row"> <div class="form-row">
<input type="date" id="day_date"> <input type="date" id="day_date">
<button type="button" onclick="genDaily()">生成日复盘</button> <button type="button" id="gen-daily-btn" onclick="genDaily()">生成日复盘</button>
<button type="button" onclick="exportDailyBundleMd()" style="background:#1f3a5a">导出当日日复盘MD</button> <button type="button" onclick="exportDailyBundleMd()" style="background:#1f3a5a">导出当日日复盘MD</button>
<input type="date" id="week_start"> <input type="date" id="week_start">
<input type="date" id="week_end"> <input type="date" id="week_end">
<button type="button" onclick="genWeekly()">生成周复盘</button> <button type="button" id="gen-weekly-btn" onclick="genWeekly()">生成周复盘</button>
<button type="button" onclick="exportWeeklyBundleMd()" style="background:#1f3a5a">导出当周复盘MD</button> <button type="button" onclick="exportWeeklyBundleMd()" style="background:#1f3a5a">导出当周复盘MD</button>
</div> </div>
<div class="ai-result-wrap" id="daily_result_wrap" style="display:none"> <div class="ai-result-wrap" id="daily_result_wrap" style="display:none">
@@ -911,7 +911,7 @@
</div> </div>
<script src="/static/instance_ui.js?v=1"></script> <script src="/static/instance_ui.js?v=1"></script>
<script src="/static/ai_review_render.js?v=1"></script> <script src="/static/ai_review_render.js?v=2"></script>
<script src="/static/form_submit_guard.js?v=2"></script> <script src="/static/form_submit_guard.js?v=2"></script>
<script> <script>
const JOURNAL_ENTRY_REASON_OPTIONS = {{ entry_reason_options | tojson }}; const JOURNAL_ENTRY_REASON_OPTIONS = {{ entry_reason_options | tojson }};
@@ -1242,36 +1242,76 @@ function loadReviews(){
} }
function genDaily(){ function genDaily(){
if(window.AiReviewRender && AiReviewRender.isGenerating && AiReviewRender.isGenerating()) return;
const d = document.getElementById("day_date").value; const d = document.getElementById("day_date").value;
if(!d){alert("请选择日期");return;} if(!d){alert("请选择日期");return;}
if(window.AiReviewRender && AiReviewRender.setGenerating){
AiReviewRender.setGenerating({
wrapId:"daily_result_wrap",
elId:"daily_result",
btnId:"gen-daily-btn",
message:"生成日复盘中,请稍候…(AI 分析可能需要 1~3 分钟)",
btnLabel:"日复盘生成中…"
});
}
fetch("/ai_daily_review",{method:"POST",headers:{"Content-Type":"application/x-www-form-urlencoded"},body:`date=${encodeURIComponent(d)}`}) fetch("/ai_daily_review",{method:"POST",headers:{"Content-Type":"application/x-www-form-urlencoded"},body:`date=${encodeURIComponent(d)}`})
.then(r=>{ if(!r.ok) throw new Error("HTTP "+r.status); return r.json(); }) .then(r=>{ if(!r.ok) throw new Error("HTTP "+r.status); return r.json(); })
.then(data=>{ .then(data=>{
if(!data || data.result == null) throw new Error("返回数据为空");
const el=document.getElementById("daily_result"); const el=document.getElementById("daily_result");
const wrap=document.getElementById("daily_result_wrap"); const wrap=document.getElementById("daily_result_wrap");
setAiReviewMarkdown(el, data.result); setAiReviewMarkdown(el, data.result);
if(wrap){ wrap.style.display="block"; } if(wrap){ wrap.style.display="block"; }
else { el.style.display="block"; } else if(el){ el.style.display="block"; }
if(window.AiReviewRender && AiReviewRender.clearGenerating) AiReviewRender.clearGenerating("gen-daily-btn");
loadReviews(); loadReviews();
}) })
.catch(e=>alert("生成日复盘失败:"+(e.message||e))); .catch(e=>{
if(window.AiReviewRender && AiReviewRender.clearGenerating) AiReviewRender.clearGenerating("gen-daily-btn");
const el=document.getElementById("daily_result");
if(el){
el.classList.remove("is-loading","ai-result-md");
el.innerText="生成失败,请重试。";
}
alert("生成日复盘失败:"+(e.message||e));
});
} }
function genWeekly(){ function genWeekly(){
if(window.AiReviewRender && AiReviewRender.isGenerating && AiReviewRender.isGenerating()) return;
const s=document.getElementById("week_start").value; const s=document.getElementById("week_start").value;
const e=document.getElementById("week_end").value; const e=document.getElementById("week_end").value;
if(!s || !e){alert("请选择起止日期");return;} if(!s || !e){alert("请选择起止日期");return;}
if(window.AiReviewRender && AiReviewRender.setGenerating){
AiReviewRender.setGenerating({
wrapId:"weekly_result_wrap",
elId:"weekly_result",
btnId:"gen-weekly-btn",
message:"生成周复盘中,请稍候…(AI 分析可能需要 1~3 分钟)",
btnLabel:"周复盘生成中…"
});
}
fetch("/ai_weekly_review",{method:"POST",headers:{"Content-Type":"application/x-www-form-urlencoded"},body:`start_date=${encodeURIComponent(s)}&end_date=${encodeURIComponent(e)}`}) fetch("/ai_weekly_review",{method:"POST",headers:{"Content-Type":"application/x-www-form-urlencoded"},body:`start_date=${encodeURIComponent(s)}&end_date=${encodeURIComponent(e)}`})
.then(r=>{ if(!r.ok) throw new Error("HTTP "+r.status); return r.json(); }) .then(r=>{ if(!r.ok) throw new Error("HTTP "+r.status); return r.json(); })
.then(data=>{ .then(data=>{
if(!data || data.result == null) throw new Error("返回数据为空");
const el=document.getElementById("weekly_result"); const el=document.getElementById("weekly_result");
const wrap=document.getElementById("weekly_result_wrap"); const wrap=document.getElementById("weekly_result_wrap");
setAiReviewMarkdown(el, data.result); setAiReviewMarkdown(el, data.result);
if(wrap){ wrap.style.display="block"; } if(wrap){ wrap.style.display="block"; }
else { el.style.display="block"; } else if(el){ el.style.display="block"; }
if(window.AiReviewRender && AiReviewRender.clearGenerating) AiReviewRender.clearGenerating("gen-weekly-btn");
loadReviews(); loadReviews();
}) })
.catch(e=>alert("生成周复盘失败:"+(e.message||e))); .catch(e=>{
if(window.AiReviewRender && AiReviewRender.clearGenerating) AiReviewRender.clearGenerating("gen-weekly-btn");
const el=document.getElementById("weekly_result");
if(el){
el.classList.remove("is-loading","ai-result-md");
el.innerText="生成失败,请重试。";
}
alert("生成周复盘失败:"+(e.message||e));
});
} }
function exportDailyBundleMd(){ function exportDailyBundleMd(){
+7 -2
View File
@@ -92,9 +92,14 @@ def sql_list_time_field(*columns):
""" """
SQLite 列表时间窗比较表达式 SQLite 列表时间窗比较表达式
journal_entries open/close 可能含 'T'直接与 bounds空格格式比会误判为超出上界 journal_entries open/close 可能含 'T'直接与 bounds空格格式比会误判为超出上界
单列时不用 COALESCESQLite 要求 COALESCE 至少 2 个参数
""" """
cols = ", ".join(columns) cols = [c for c in columns if c]
return f"REPLACE(COALESCE({cols}), 'T', ' ')" if not cols:
raise ValueError("sql_list_time_field requires at least one column")
if len(cols) == 1:
return f"REPLACE({cols[0]}, 'T', ' ')"
return f"REPLACE(COALESCE({', '.join(cols)}), 'T', ' ')"
SESSION_KEY_LIST_WIN = "list_win_filter" SESSION_KEY_LIST_WIN = "list_win_filter"
+47
View File
@@ -109,10 +109,54 @@
return html.join("\n"); return html.join("\n");
} }
var _genBusy = false;
function setGenerating(opts) {
opts = opts || {};
_genBusy = true;
var wrap = document.getElementById(opts.wrapId);
var el = document.getElementById(opts.elId);
var btn = opts.btnId ? document.getElementById(opts.btnId) : null;
if (wrap) wrap.style.display = "block";
if (el) {
el.classList.remove("ai-result-md");
el.classList.add("is-loading");
el.innerHTML = "";
el.innerText = opts.message || "生成复盘中,请稍候…";
}
if (btn) {
btn.disabled = true;
if (!btn.dataset.aiOrigText) btn.dataset.aiOrigText = btn.textContent;
btn.textContent = opts.btnLabel || "生成中…";
}
if (wrap && wrap.scrollIntoView) {
try {
wrap.scrollIntoView({ behavior: "smooth", block: "nearest" });
} catch (e) { /* ignore */ }
}
}
function clearGenerating(btnId) {
_genBusy = false;
var btn = btnId ? document.getElementById(btnId) : null;
if (btn) {
btn.disabled = false;
if (btn.dataset.aiOrigText) {
btn.textContent = btn.dataset.aiOrigText;
delete btn.dataset.aiOrigText;
}
}
}
function isGenerating() {
return _genBusy;
}
function setElementMarkdown(el, rawText) { function setElementMarkdown(el, rawText) {
if (!el) return; if (!el) return;
var raw = String(rawText || ""); var raw = String(rawText || "");
el.dataset.markdownRaw = raw; el.dataset.markdownRaw = raw;
el.classList.remove("is-loading");
el.classList.add("ai-result-md"); el.classList.add("ai-result-md");
el.innerHTML = renderMarkdown(raw); el.innerHTML = renderMarkdown(raw);
} }
@@ -130,5 +174,8 @@
renderMarkdown: renderMarkdown, renderMarkdown: renderMarkdown,
setElementMarkdown: setElementMarkdown, setElementMarkdown: setElementMarkdown,
getElementMarkdown: getElementMarkdown, getElementMarkdown: getElementMarkdown,
setGenerating: setGenerating,
clearGenerating: clearGenerating,
isGenerating: isGenerating,
}; };
})(typeof window !== "undefined" ? window : this); })(typeof window !== "undefined" ? window : this);
+15
View File
@@ -601,6 +601,21 @@ html[data-theme="light"] .ai-result {
color: #1a2838 !important; color: #1a2838 !important;
} }
.ai-result.is-loading {
color: #8fc8ff;
font-style: italic;
animation: ai-review-pulse 1.2s ease-in-out infinite;
}
html[data-theme="light"] .ai-result.is-loading {
color: #006e9a !important;
}
@keyframes ai-review-pulse {
0%, 100% { opacity: 1; }
50% { opacity: 0.55; }
}
html[data-theme="light"] .ai-result-md p, html[data-theme="light"] .ai-result-md p,
html[data-theme="light"] .detail-modal .panel-body.md-review p { html[data-theme="light"] .detail-modal .panel-body.md-review p {
color: #1a2838 !important; color: #1a2838 !important;