fix: AI review list UTC filter, vision timeout, and stuck loading state
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
+8
-3
@@ -19,7 +19,12 @@ def _env_str(name: str, default: str = "") -> str:
|
|||||||
return str(v).strip()
|
return str(v).strip()
|
||||||
|
|
||||||
|
|
||||||
def _ai_timeout_seconds() -> int:
|
def _ai_timeout_seconds(*, image_count: int = 0) -> int:
|
||||||
|
if image_count > 0:
|
||||||
|
try:
|
||||||
|
return max(30, int(_env_str("AI_REVIEW_TIMEOUT_SECONDS", "300") or "300"))
|
||||||
|
except ValueError:
|
||||||
|
return 300
|
||||||
try:
|
try:
|
||||||
return max(10, int(_env_str("AI_TIMEOUT_SECONDS", "120") or "120"))
|
return max(10, int(_env_str("AI_TIMEOUT_SECONDS", "120") or "120"))
|
||||||
except ValueError:
|
except ValueError:
|
||||||
@@ -129,7 +134,7 @@ def _generate_openai(prompt: str, images: List[tuple], temperature: float) -> st
|
|||||||
_openai_chat_url(),
|
_openai_chat_url(),
|
||||||
headers=headers,
|
headers=headers,
|
||||||
json=body,
|
json=body,
|
||||||
timeout=_ai_timeout_seconds(),
|
timeout=_ai_timeout_seconds(image_count=len(images)),
|
||||||
)
|
)
|
||||||
r.raise_for_status()
|
r.raise_for_status()
|
||||||
data = r.json()
|
data = r.json()
|
||||||
@@ -149,7 +154,7 @@ def _generate_ollama(prompt: str, images: List[tuple], temperature: float) -> st
|
|||||||
}
|
}
|
||||||
if images:
|
if images:
|
||||||
payload["images"] = [b64 for b64, _mime in images]
|
payload["images"] = [b64 for b64, _mime in images]
|
||||||
r = requests.post(_ollama_api(), json=payload, timeout=_ai_timeout_seconds())
|
r = requests.post(_ollama_api(), json=payload, timeout=_ai_timeout_seconds(image_count=len(images)))
|
||||||
r.raise_for_status()
|
r.raise_for_status()
|
||||||
return (r.json().get("response") or "").strip() or "AI 生成失败"
|
return (r.json().get("response") or "").strip() or "AI 生成失败"
|
||||||
|
|
||||||
|
|||||||
@@ -138,6 +138,7 @@ from history_window_lib import (
|
|||||||
resolve_window,
|
resolve_window,
|
||||||
sql_list_time_field,
|
sql_list_time_field,
|
||||||
utc_window_to_bj_sql_strings,
|
utc_window_to_bj_sql_strings,
|
||||||
|
utc_window_to_utc_sql_strings,
|
||||||
)
|
)
|
||||||
|
|
||||||
def load_env_file(path):
|
def load_env_file(path):
|
||||||
@@ -7791,11 +7792,11 @@ def delete_journal(jid):
|
|||||||
@login_required
|
@login_required
|
||||||
def api_reviews():
|
def api_reviews():
|
||||||
win = _list_window_from_request()
|
win = _list_window_from_request()
|
||||||
start_bj, end_bj = utc_window_to_bj_sql_strings(win["start_utc"], win["end_utc"], APP_TZ)
|
start_sql, end_sql = utc_window_to_utc_sql_strings(win["start_utc"], win["end_utc"])
|
||||||
conn = get_db()
|
conn = get_db()
|
||||||
rows = conn.execute(
|
rows = conn.execute(
|
||||||
"SELECT * FROM ai_reviews WHERE created_at >= ? AND created_at <= ? ORDER BY created_at DESC LIMIT 200",
|
"SELECT * FROM ai_reviews WHERE created_at >= ? AND created_at <= ? ORDER BY created_at DESC LIMIT 200",
|
||||||
(start_bj, end_bj),
|
(start_sql, end_sql),
|
||||||
).fetchall()
|
).fetchall()
|
||||||
conn.close()
|
conn.close()
|
||||||
return jsonify([row_to_dict(r) for r in rows])
|
return jsonify([row_to_dict(r) for r in rows])
|
||||||
|
|||||||
@@ -1245,7 +1245,9 @@ function genDaily(){
|
|||||||
btnLabel:"日复盘生成中…"
|
btnLabel:"日复盘生成中…"
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
fetch("/ai_daily_review",{method:"POST",headers:{"Content-Type":"application/x-www-form-urlencoded"},body:`date=${encodeURIComponent(d)}`})
|
const ac = new AbortController();
|
||||||
|
const timer = setTimeout(()=>ac.abort(), 360000);
|
||||||
|
fetch("/ai_daily_review",{method:"POST",headers:{"Content-Type":"application/x-www-form-urlencoded"},body:`date=${encodeURIComponent(d)}`,signal:ac.signal})
|
||||||
.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("返回数据为空");
|
if(!data || data.result == null) throw new Error("返回数据为空");
|
||||||
@@ -1254,17 +1256,21 @@ function genDaily(){
|
|||||||
setAiReviewMarkdown(el, data.result);
|
setAiReviewMarkdown(el, data.result);
|
||||||
if(wrap){ wrap.style.display="block"; }
|
if(wrap){ wrap.style.display="block"; }
|
||||||
else if(el){ 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=>{
|
.catch(err=>{
|
||||||
if(window.AiReviewRender && AiReviewRender.clearGenerating) AiReviewRender.clearGenerating("gen-daily-btn");
|
|
||||||
const el=document.getElementById("daily_result");
|
const el=document.getElementById("daily_result");
|
||||||
if(el){
|
if(el && el.classList.contains("is-loading")){
|
||||||
el.classList.remove("is-loading","ai-result-md");
|
el.classList.remove("is-loading","ai-result-md");
|
||||||
el.innerText="生成失败,请重试。";
|
el.innerText = err.name === "AbortError"
|
||||||
|
? "生成超时(>6分钟),请检查 OPENAI_MODEL 是否与网关已启用模型一致,或增大 AI_REVIEW_TIMEOUT_SECONDS。"
|
||||||
|
: "生成失败,请重试。";
|
||||||
}
|
}
|
||||||
alert("生成日复盘失败:"+(e.message||e));
|
alert("生成日复盘失败:"+(err.message||err));
|
||||||
|
})
|
||||||
|
.finally(()=>{
|
||||||
|
clearTimeout(timer);
|
||||||
|
if(window.AiReviewRender && AiReviewRender.clearGenerating) AiReviewRender.clearGenerating("gen-daily-btn");
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1282,7 +1288,9 @@ function genWeekly(){
|
|||||||
btnLabel:"周复盘生成中…"
|
btnLabel:"周复盘生成中…"
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
fetch("/ai_weekly_review",{method:"POST",headers:{"Content-Type":"application/x-www-form-urlencoded"},body:`start_date=${encodeURIComponent(s)}&end_date=${encodeURIComponent(e)}`})
|
const ac = new AbortController();
|
||||||
|
const timer = setTimeout(()=>ac.abort(), 360000);
|
||||||
|
fetch("/ai_weekly_review",{method:"POST",headers:{"Content-Type":"application/x-www-form-urlencoded"},body:`start_date=${encodeURIComponent(s)}&end_date=${encodeURIComponent(e)}`,signal:ac.signal})
|
||||||
.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("返回数据为空");
|
if(!data || data.result == null) throw new Error("返回数据为空");
|
||||||
@@ -1291,17 +1299,21 @@ function genWeekly(){
|
|||||||
setAiReviewMarkdown(el, data.result);
|
setAiReviewMarkdown(el, data.result);
|
||||||
if(wrap){ wrap.style.display="block"; }
|
if(wrap){ wrap.style.display="block"; }
|
||||||
else if(el){ 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=>{
|
.catch(err=>{
|
||||||
if(window.AiReviewRender && AiReviewRender.clearGenerating) AiReviewRender.clearGenerating("gen-weekly-btn");
|
|
||||||
const el=document.getElementById("weekly_result");
|
const el=document.getElementById("weekly_result");
|
||||||
if(el){
|
if(el && el.classList.contains("is-loading")){
|
||||||
el.classList.remove("is-loading","ai-result-md");
|
el.classList.remove("is-loading","ai-result-md");
|
||||||
el.innerText="生成失败,请重试。";
|
el.innerText = err.name === "AbortError"
|
||||||
|
? "生成超时(>6分钟),请检查 OPENAI_MODEL 是否与网关已启用模型一致,或增大 AI_REVIEW_TIMEOUT_SECONDS。"
|
||||||
|
: "生成失败,请重试。";
|
||||||
}
|
}
|
||||||
alert("生成周复盘失败:"+(e.message||e));
|
alert("生成周复盘失败:"+(err.message||err));
|
||||||
|
})
|
||||||
|
.finally(()=>{
|
||||||
|
clearTimeout(timer);
|
||||||
|
if(window.AiReviewRender && AiReviewRender.clearGenerating) AiReviewRender.clearGenerating("gen-weekly-btn");
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -137,6 +137,7 @@ from history_window_lib import (
|
|||||||
resolve_window,
|
resolve_window,
|
||||||
sql_list_time_field,
|
sql_list_time_field,
|
||||||
utc_window_to_bj_sql_strings,
|
utc_window_to_bj_sql_strings,
|
||||||
|
utc_window_to_utc_sql_strings,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@@ -7841,11 +7842,11 @@ def delete_journal(jid):
|
|||||||
@login_required
|
@login_required
|
||||||
def api_reviews():
|
def api_reviews():
|
||||||
win = _list_window_from_request()
|
win = _list_window_from_request()
|
||||||
start_bj, end_bj = utc_window_to_bj_sql_strings(win["start_utc"], win["end_utc"], APP_TZ)
|
start_sql, end_sql = utc_window_to_utc_sql_strings(win["start_utc"], win["end_utc"])
|
||||||
conn = get_db()
|
conn = get_db()
|
||||||
rows = conn.execute(
|
rows = conn.execute(
|
||||||
"SELECT * FROM ai_reviews WHERE created_at >= ? AND created_at <= ? ORDER BY created_at DESC LIMIT 200",
|
"SELECT * FROM ai_reviews WHERE created_at >= ? AND created_at <= ? ORDER BY created_at DESC LIMIT 200",
|
||||||
(start_bj, end_bj),
|
(start_sql, end_sql),
|
||||||
).fetchall()
|
).fetchall()
|
||||||
conn.close()
|
conn.close()
|
||||||
return jsonify([row_to_dict(r) for r in rows])
|
return jsonify([row_to_dict(r) for r in rows])
|
||||||
|
|||||||
@@ -1245,7 +1245,9 @@ function genDaily(){
|
|||||||
btnLabel:"日复盘生成中…"
|
btnLabel:"日复盘生成中…"
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
fetch("/ai_daily_review",{method:"POST",headers:{"Content-Type":"application/x-www-form-urlencoded"},body:`date=${encodeURIComponent(d)}`})
|
const ac = new AbortController();
|
||||||
|
const timer = setTimeout(()=>ac.abort(), 360000);
|
||||||
|
fetch("/ai_daily_review",{method:"POST",headers:{"Content-Type":"application/x-www-form-urlencoded"},body:`date=${encodeURIComponent(d)}`,signal:ac.signal})
|
||||||
.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("返回数据为空");
|
if(!data || data.result == null) throw new Error("返回数据为空");
|
||||||
@@ -1254,17 +1256,21 @@ function genDaily(){
|
|||||||
setAiReviewMarkdown(el, data.result);
|
setAiReviewMarkdown(el, data.result);
|
||||||
if(wrap){ wrap.style.display="block"; }
|
if(wrap){ wrap.style.display="block"; }
|
||||||
else if(el){ 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=>{
|
.catch(err=>{
|
||||||
if(window.AiReviewRender && AiReviewRender.clearGenerating) AiReviewRender.clearGenerating("gen-daily-btn");
|
|
||||||
const el=document.getElementById("daily_result");
|
const el=document.getElementById("daily_result");
|
||||||
if(el){
|
if(el && el.classList.contains("is-loading")){
|
||||||
el.classList.remove("is-loading","ai-result-md");
|
el.classList.remove("is-loading","ai-result-md");
|
||||||
el.innerText="生成失败,请重试。";
|
el.innerText = err.name === "AbortError"
|
||||||
|
? "生成超时(>6分钟),请检查 OPENAI_MODEL 是否与网关已启用模型一致,或增大 AI_REVIEW_TIMEOUT_SECONDS。"
|
||||||
|
: "生成失败,请重试。";
|
||||||
}
|
}
|
||||||
alert("生成日复盘失败:"+(e.message||e));
|
alert("生成日复盘失败:"+(err.message||err));
|
||||||
|
})
|
||||||
|
.finally(()=>{
|
||||||
|
clearTimeout(timer);
|
||||||
|
if(window.AiReviewRender && AiReviewRender.clearGenerating) AiReviewRender.clearGenerating("gen-daily-btn");
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1282,7 +1288,9 @@ function genWeekly(){
|
|||||||
btnLabel:"周复盘生成中…"
|
btnLabel:"周复盘生成中…"
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
fetch("/ai_weekly_review",{method:"POST",headers:{"Content-Type":"application/x-www-form-urlencoded"},body:`start_date=${encodeURIComponent(s)}&end_date=${encodeURIComponent(e)}`})
|
const ac = new AbortController();
|
||||||
|
const timer = setTimeout(()=>ac.abort(), 360000);
|
||||||
|
fetch("/ai_weekly_review",{method:"POST",headers:{"Content-Type":"application/x-www-form-urlencoded"},body:`start_date=${encodeURIComponent(s)}&end_date=${encodeURIComponent(e)}`,signal:ac.signal})
|
||||||
.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("返回数据为空");
|
if(!data || data.result == null) throw new Error("返回数据为空");
|
||||||
@@ -1291,17 +1299,21 @@ function genWeekly(){
|
|||||||
setAiReviewMarkdown(el, data.result);
|
setAiReviewMarkdown(el, data.result);
|
||||||
if(wrap){ wrap.style.display="block"; }
|
if(wrap){ wrap.style.display="block"; }
|
||||||
else if(el){ 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=>{
|
.catch(err=>{
|
||||||
if(window.AiReviewRender && AiReviewRender.clearGenerating) AiReviewRender.clearGenerating("gen-weekly-btn");
|
|
||||||
const el=document.getElementById("weekly_result");
|
const el=document.getElementById("weekly_result");
|
||||||
if(el){
|
if(el && el.classList.contains("is-loading")){
|
||||||
el.classList.remove("is-loading","ai-result-md");
|
el.classList.remove("is-loading","ai-result-md");
|
||||||
el.innerText="生成失败,请重试。";
|
el.innerText = err.name === "AbortError"
|
||||||
|
? "生成超时(>6分钟),请检查 OPENAI_MODEL 是否与网关已启用模型一致,或增大 AI_REVIEW_TIMEOUT_SECONDS。"
|
||||||
|
: "生成失败,请重试。";
|
||||||
}
|
}
|
||||||
alert("生成周复盘失败:"+(e.message||e));
|
alert("生成周复盘失败:"+(err.message||err));
|
||||||
|
})
|
||||||
|
.finally(()=>{
|
||||||
|
clearTimeout(timer);
|
||||||
|
if(window.AiReviewRender && AiReviewRender.clearGenerating) AiReviewRender.clearGenerating("gen-weekly-btn");
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -125,11 +125,13 @@ FORCE_CLOSE_ENABLED=false
|
|||||||
# 推送与AI超时(秒)
|
# 推送与AI超时(秒)
|
||||||
WECHAT_TIMEOUT_SECONDS=10
|
WECHAT_TIMEOUT_SECONDS=10
|
||||||
AI_TIMEOUT_SECONDS=120
|
AI_TIMEOUT_SECONDS=120
|
||||||
|
AI_REVIEW_TIMEOUT_SECONDS=300
|
||||||
|
|
||||||
AI_PROVIDER=openai
|
AI_PROVIDER=openai
|
||||||
OPENAI_API_BASE=https://op.bz121.com/v1
|
OPENAI_API_BASE=https://op.bz121.com/v1
|
||||||
OPENAI_API_KEY=你的密钥
|
OPENAI_API_KEY=你的密钥
|
||||||
OPENAI_MODEL=gemma4:e4b
|
# 须与网关「模型分布」里已启用节点的 Model ID 完全一致(示例为 4070s 节点)
|
||||||
|
OPENAI_MODEL=huihui_ai/gemma-4-abliterated:e4b
|
||||||
OLLAMA_API=http://127.0.0.1:11434/api/generate
|
OLLAMA_API=http://127.0.0.1:11434/api/generate
|
||||||
AI_MODEL=huihui_ai/deepseek-r1-abliterated:latest
|
AI_MODEL=huihui_ai/deepseek-r1-abliterated:latest
|
||||||
|
|
||||||
|
|||||||
@@ -100,6 +100,7 @@ from history_window_lib import (
|
|||||||
resolve_window,
|
resolve_window,
|
||||||
sql_list_time_field,
|
sql_list_time_field,
|
||||||
utc_window_to_bj_sql_strings,
|
utc_window_to_bj_sql_strings,
|
||||||
|
utc_window_to_utc_sql_strings,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@@ -7720,11 +7721,11 @@ def delete_journal(jid):
|
|||||||
@login_required
|
@login_required
|
||||||
def api_reviews():
|
def api_reviews():
|
||||||
win = _list_window_from_request()
|
win = _list_window_from_request()
|
||||||
start_bj, end_bj = utc_window_to_bj_sql_strings(win["start_utc"], win["end_utc"], APP_TZ)
|
start_sql, end_sql = utc_window_to_utc_sql_strings(win["start_utc"], win["end_utc"])
|
||||||
conn = get_db()
|
conn = get_db()
|
||||||
rows = conn.execute(
|
rows = conn.execute(
|
||||||
"SELECT * FROM ai_reviews WHERE created_at >= ? AND created_at <= ? ORDER BY created_at DESC LIMIT 200",
|
"SELECT * FROM ai_reviews WHERE created_at >= ? AND created_at <= ? ORDER BY created_at DESC LIMIT 200",
|
||||||
(start_bj, end_bj),
|
(start_sql, end_sql),
|
||||||
).fetchall()
|
).fetchall()
|
||||||
conn.close()
|
conn.close()
|
||||||
return jsonify([row_to_dict(r) for r in rows])
|
return jsonify([row_to_dict(r) for r in rows])
|
||||||
@@ -7992,10 +7993,11 @@ def _journal_ai_chart_builder(row):
|
|||||||
@login_required
|
@login_required
|
||||||
def ai_daily_review():
|
def ai_daily_review():
|
||||||
date = request.form.get("date", "")
|
date = request.form.get("date", "")
|
||||||
|
try:
|
||||||
conn = get_db()
|
conn = get_db()
|
||||||
rows = conn.execute(
|
rows = conn.execute(
|
||||||
"SELECT * FROM journal_entries WHERE substr(open_datetime, 1, 10)=? ORDER BY open_datetime ASC",
|
"SELECT * FROM journal_entries WHERE substr(open_datetime, 1, 10)=? ORDER BY open_datetime ASC",
|
||||||
(date,)
|
(date,),
|
||||||
).fetchall()
|
).fetchall()
|
||||||
conn.close()
|
conn.close()
|
||||||
if not rows:
|
if not rows:
|
||||||
@@ -8011,16 +8013,20 @@ def ai_daily_review():
|
|||||||
app.config["UPLOAD_FOLDER"],
|
app.config["UPLOAD_FOLDER"],
|
||||||
build_chart_if_missing=_journal_ai_chart_builder,
|
build_chart_if_missing=_journal_ai_chart_builder,
|
||||||
)
|
)
|
||||||
|
print(f"[ai_daily_review] date={date} rows={len(rows)} images={len(image_paths)}")
|
||||||
ai_result = ai_review(text, "每日", image_paths=image_paths)
|
ai_result = ai_review(text, "每日", image_paths=image_paths)
|
||||||
full = f"【AI日复盘 {date}】\n{ai_result}\n\n原始记录:\n{text}"
|
full = f"【AI日复盘 {date}】\n{ai_result}\n\n原始记录:\n{text}"
|
||||||
conn = get_db()
|
conn = get_db()
|
||||||
conn.execute(
|
conn.execute(
|
||||||
"INSERT INTO ai_reviews (id, review_type, target_date, content) VALUES (?,?,?,?)",
|
"INSERT INTO ai_reviews (id, review_type, target_date, content) VALUES (?,?,?,?)",
|
||||||
(uuid.uuid4().hex, "daily", date, full)
|
(uuid.uuid4().hex, "daily", date, full),
|
||||||
)
|
)
|
||||||
conn.commit()
|
conn.commit()
|
||||||
conn.close()
|
conn.close()
|
||||||
return jsonify({"result": full})
|
return jsonify({"result": full})
|
||||||
|
except Exception as e:
|
||||||
|
print(f"[ai_daily_review] date={date} failed: {e}")
|
||||||
|
return jsonify({"ok": False, "result": f"生成失败:{e}"}), 500
|
||||||
|
|
||||||
|
|
||||||
@app.route("/ai_weekly_review", methods=["POST"])
|
@app.route("/ai_weekly_review", methods=["POST"])
|
||||||
@@ -8028,10 +8034,11 @@ def ai_daily_review():
|
|||||||
def ai_weekly_review():
|
def ai_weekly_review():
|
||||||
start_date = request.form.get("start_date", "")
|
start_date = request.form.get("start_date", "")
|
||||||
end_date = request.form.get("end_date", "")
|
end_date = request.form.get("end_date", "")
|
||||||
|
try:
|
||||||
conn = get_db()
|
conn = get_db()
|
||||||
rows = conn.execute(
|
rows = conn.execute(
|
||||||
"SELECT * FROM journal_entries WHERE substr(open_datetime,1,10) >= ? AND substr(open_datetime,1,10) <= ? ORDER BY open_datetime ASC",
|
"SELECT * FROM journal_entries WHERE substr(open_datetime,1,10) >= ? AND substr(open_datetime,1,10) <= ? ORDER BY open_datetime ASC",
|
||||||
(start_date, end_date)
|
(start_date, end_date),
|
||||||
).fetchall()
|
).fetchall()
|
||||||
conn.close()
|
conn.close()
|
||||||
if not rows:
|
if not rows:
|
||||||
@@ -8047,16 +8054,20 @@ def ai_weekly_review():
|
|||||||
app.config["UPLOAD_FOLDER"],
|
app.config["UPLOAD_FOLDER"],
|
||||||
build_chart_if_missing=_journal_ai_chart_builder,
|
build_chart_if_missing=_journal_ai_chart_builder,
|
||||||
)
|
)
|
||||||
|
print(f"[ai_weekly_review] range={start_date}~{end_date} rows={len(rows)} images={len(image_paths)}")
|
||||||
ai_result = ai_review(text, "周度", image_paths=image_paths)
|
ai_result = ai_review(text, "周度", image_paths=image_paths)
|
||||||
full = f"【AI周复盘 {start_date}~{end_date}】\n{ai_result}\n\n原始记录:\n{text}"
|
full = f"【AI周复盘 {start_date}~{end_date}】\n{ai_result}\n\n原始记录:\n{text}"
|
||||||
conn = get_db()
|
conn = get_db()
|
||||||
conn.execute(
|
conn.execute(
|
||||||
"INSERT INTO ai_reviews (id, review_type, target_date, content) VALUES (?,?,?,?)",
|
"INSERT INTO ai_reviews (id, review_type, target_date, content) VALUES (?,?,?,?)",
|
||||||
(uuid.uuid4().hex, "weekly", f"{start_date}~{end_date}", full)
|
(uuid.uuid4().hex, "weekly", f"{start_date}~{end_date}", full),
|
||||||
)
|
)
|
||||||
conn.commit()
|
conn.commit()
|
||||||
conn.close()
|
conn.close()
|
||||||
return jsonify({"result": full})
|
return jsonify({"result": full})
|
||||||
|
except Exception as e:
|
||||||
|
print(f"[ai_weekly_review] range={start_date}~{end_date} failed: {e}")
|
||||||
|
return jsonify({"ok": False, "result": f"生成失败:{e}"}), 500
|
||||||
|
|
||||||
def _hub_meta_bundle():
|
def _hub_meta_bundle():
|
||||||
return {
|
return {
|
||||||
|
|||||||
@@ -1208,7 +1208,9 @@ function genDaily(){
|
|||||||
btnLabel:"日复盘生成中…"
|
btnLabel:"日复盘生成中…"
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
fetch("/ai_daily_review",{method:"POST",headers:{"Content-Type":"application/x-www-form-urlencoded"},body:`date=${encodeURIComponent(d)}`})
|
const ac = new AbortController();
|
||||||
|
const timer = setTimeout(()=>ac.abort(), 360000);
|
||||||
|
fetch("/ai_daily_review",{method:"POST",headers:{"Content-Type":"application/x-www-form-urlencoded"},body:`date=${encodeURIComponent(d)}`,signal:ac.signal})
|
||||||
.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("返回数据为空");
|
if(!data || data.result == null) throw new Error("返回数据为空");
|
||||||
@@ -1217,17 +1219,21 @@ function genDaily(){
|
|||||||
setAiReviewMarkdown(el, data.result);
|
setAiReviewMarkdown(el, data.result);
|
||||||
if(wrap){ wrap.style.display="block"; }
|
if(wrap){ wrap.style.display="block"; }
|
||||||
else if(el){ 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=>{
|
.catch(err=>{
|
||||||
if(window.AiReviewRender && AiReviewRender.clearGenerating) AiReviewRender.clearGenerating("gen-daily-btn");
|
|
||||||
const el=document.getElementById("daily_result");
|
const el=document.getElementById("daily_result");
|
||||||
if(el){
|
if(el && el.classList.contains("is-loading")){
|
||||||
el.classList.remove("is-loading","ai-result-md");
|
el.classList.remove("is-loading","ai-result-md");
|
||||||
el.innerText="生成失败,请重试。";
|
el.innerText = err.name === "AbortError"
|
||||||
|
? "生成超时(>6分钟),请检查 OPENAI_MODEL 是否与网关已启用模型一致,或增大 AI_REVIEW_TIMEOUT_SECONDS。"
|
||||||
|
: "生成失败,请重试。";
|
||||||
}
|
}
|
||||||
alert("生成日复盘失败:"+(e.message||e));
|
alert("生成日复盘失败:"+(err.message||err));
|
||||||
|
})
|
||||||
|
.finally(()=>{
|
||||||
|
clearTimeout(timer);
|
||||||
|
if(window.AiReviewRender && AiReviewRender.clearGenerating) AiReviewRender.clearGenerating("gen-daily-btn");
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1245,7 +1251,9 @@ function genWeekly(){
|
|||||||
btnLabel:"周复盘生成中…"
|
btnLabel:"周复盘生成中…"
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
fetch("/ai_weekly_review",{method:"POST",headers:{"Content-Type":"application/x-www-form-urlencoded"},body:`start_date=${encodeURIComponent(s)}&end_date=${encodeURIComponent(e)}`})
|
const ac = new AbortController();
|
||||||
|
const timer = setTimeout(()=>ac.abort(), 360000);
|
||||||
|
fetch("/ai_weekly_review",{method:"POST",headers:{"Content-Type":"application/x-www-form-urlencoded"},body:`start_date=${encodeURIComponent(s)}&end_date=${encodeURIComponent(e)}`,signal:ac.signal})
|
||||||
.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("返回数据为空");
|
if(!data || data.result == null) throw new Error("返回数据为空");
|
||||||
@@ -1254,17 +1262,21 @@ function genWeekly(){
|
|||||||
setAiReviewMarkdown(el, data.result);
|
setAiReviewMarkdown(el, data.result);
|
||||||
if(wrap){ wrap.style.display="block"; }
|
if(wrap){ wrap.style.display="block"; }
|
||||||
else if(el){ 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=>{
|
.catch(err=>{
|
||||||
if(window.AiReviewRender && AiReviewRender.clearGenerating) AiReviewRender.clearGenerating("gen-weekly-btn");
|
|
||||||
const el=document.getElementById("weekly_result");
|
const el=document.getElementById("weekly_result");
|
||||||
if(el){
|
if(el && el.classList.contains("is-loading")){
|
||||||
el.classList.remove("is-loading","ai-result-md");
|
el.classList.remove("is-loading","ai-result-md");
|
||||||
el.innerText="生成失败,请重试。";
|
el.innerText = err.name === "AbortError"
|
||||||
|
? "生成超时(>6分钟),请检查 OPENAI_MODEL 是否与网关已启用模型一致,或增大 AI_REVIEW_TIMEOUT_SECONDS。"
|
||||||
|
: "生成失败,请重试。";
|
||||||
}
|
}
|
||||||
alert("生成周复盘失败:"+(e.message||e));
|
alert("生成周复盘失败:"+(err.message||err));
|
||||||
|
})
|
||||||
|
.finally(()=>{
|
||||||
|
clearTimeout(timer);
|
||||||
|
if(window.AiReviewRender && AiReviewRender.clearGenerating) AiReviewRender.clearGenerating("gen-weekly-btn");
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -137,6 +137,7 @@ from history_window_lib import (
|
|||||||
resolve_window,
|
resolve_window,
|
||||||
sql_list_time_field,
|
sql_list_time_field,
|
||||||
utc_window_to_bj_sql_strings,
|
utc_window_to_bj_sql_strings,
|
||||||
|
utc_window_to_utc_sql_strings,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@@ -7473,11 +7474,11 @@ def delete_journal(jid):
|
|||||||
@login_required
|
@login_required
|
||||||
def api_reviews():
|
def api_reviews():
|
||||||
win = _list_window_from_request()
|
win = _list_window_from_request()
|
||||||
start_bj, end_bj = utc_window_to_bj_sql_strings(win["start_utc"], win["end_utc"], APP_TZ)
|
start_sql, end_sql = utc_window_to_utc_sql_strings(win["start_utc"], win["end_utc"])
|
||||||
conn = get_db()
|
conn = get_db()
|
||||||
rows = conn.execute(
|
rows = conn.execute(
|
||||||
"SELECT * FROM ai_reviews WHERE created_at >= ? AND created_at <= ? ORDER BY created_at DESC LIMIT 200",
|
"SELECT * FROM ai_reviews WHERE created_at >= ? AND created_at <= ? ORDER BY created_at DESC LIMIT 200",
|
||||||
(start_bj, end_bj),
|
(start_sql, end_sql),
|
||||||
).fetchall()
|
).fetchall()
|
||||||
conn.close()
|
conn.close()
|
||||||
return jsonify([row_to_dict(r) for r in rows])
|
return jsonify([row_to_dict(r) for r in rows])
|
||||||
|
|||||||
@@ -1254,7 +1254,9 @@ function genDaily(){
|
|||||||
btnLabel:"日复盘生成中…"
|
btnLabel:"日复盘生成中…"
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
fetch("/ai_daily_review",{method:"POST",headers:{"Content-Type":"application/x-www-form-urlencoded"},body:`date=${encodeURIComponent(d)}`})
|
const ac = new AbortController();
|
||||||
|
const timer = setTimeout(()=>ac.abort(), 360000);
|
||||||
|
fetch("/ai_daily_review",{method:"POST",headers:{"Content-Type":"application/x-www-form-urlencoded"},body:`date=${encodeURIComponent(d)}`,signal:ac.signal})
|
||||||
.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("返回数据为空");
|
if(!data || data.result == null) throw new Error("返回数据为空");
|
||||||
@@ -1263,17 +1265,21 @@ function genDaily(){
|
|||||||
setAiReviewMarkdown(el, data.result);
|
setAiReviewMarkdown(el, data.result);
|
||||||
if(wrap){ wrap.style.display="block"; }
|
if(wrap){ wrap.style.display="block"; }
|
||||||
else if(el){ 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=>{
|
.catch(err=>{
|
||||||
if(window.AiReviewRender && AiReviewRender.clearGenerating) AiReviewRender.clearGenerating("gen-daily-btn");
|
|
||||||
const el=document.getElementById("daily_result");
|
const el=document.getElementById("daily_result");
|
||||||
if(el){
|
if(el && el.classList.contains("is-loading")){
|
||||||
el.classList.remove("is-loading","ai-result-md");
|
el.classList.remove("is-loading","ai-result-md");
|
||||||
el.innerText="生成失败,请重试。";
|
el.innerText = err.name === "AbortError"
|
||||||
|
? "生成超时(>6分钟),请检查 OPENAI_MODEL 是否与网关已启用模型一致,或增大 AI_REVIEW_TIMEOUT_SECONDS。"
|
||||||
|
: "生成失败,请重试。";
|
||||||
}
|
}
|
||||||
alert("生成日复盘失败:"+(e.message||e));
|
alert("生成日复盘失败:"+(err.message||err));
|
||||||
|
})
|
||||||
|
.finally(()=>{
|
||||||
|
clearTimeout(timer);
|
||||||
|
if(window.AiReviewRender && AiReviewRender.clearGenerating) AiReviewRender.clearGenerating("gen-daily-btn");
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1291,7 +1297,9 @@ function genWeekly(){
|
|||||||
btnLabel:"周复盘生成中…"
|
btnLabel:"周复盘生成中…"
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
fetch("/ai_weekly_review",{method:"POST",headers:{"Content-Type":"application/x-www-form-urlencoded"},body:`start_date=${encodeURIComponent(s)}&end_date=${encodeURIComponent(e)}`})
|
const ac = new AbortController();
|
||||||
|
const timer = setTimeout(()=>ac.abort(), 360000);
|
||||||
|
fetch("/ai_weekly_review",{method:"POST",headers:{"Content-Type":"application/x-www-form-urlencoded"},body:`start_date=${encodeURIComponent(s)}&end_date=${encodeURIComponent(e)}`,signal:ac.signal})
|
||||||
.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("返回数据为空");
|
if(!data || data.result == null) throw new Error("返回数据为空");
|
||||||
@@ -1300,17 +1308,21 @@ function genWeekly(){
|
|||||||
setAiReviewMarkdown(el, data.result);
|
setAiReviewMarkdown(el, data.result);
|
||||||
if(wrap){ wrap.style.display="block"; }
|
if(wrap){ wrap.style.display="block"; }
|
||||||
else if(el){ 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=>{
|
.catch(err=>{
|
||||||
if(window.AiReviewRender && AiReviewRender.clearGenerating) AiReviewRender.clearGenerating("gen-weekly-btn");
|
|
||||||
const el=document.getElementById("weekly_result");
|
const el=document.getElementById("weekly_result");
|
||||||
if(el){
|
if(el && el.classList.contains("is-loading")){
|
||||||
el.classList.remove("is-loading","ai-result-md");
|
el.classList.remove("is-loading","ai-result-md");
|
||||||
el.innerText="生成失败,请重试。";
|
el.innerText = err.name === "AbortError"
|
||||||
|
? "生成超时(>6分钟),请检查 OPENAI_MODEL 是否与网关已启用模型一致,或增大 AI_REVIEW_TIMEOUT_SECONDS。"
|
||||||
|
: "生成失败,请重试。";
|
||||||
}
|
}
|
||||||
alert("生成周复盘失败:"+(e.message||e));
|
alert("生成周复盘失败:"+(err.message||err));
|
||||||
|
})
|
||||||
|
.finally(()=>{
|
||||||
|
clearTimeout(timer);
|
||||||
|
if(window.AiReviewRender && AiReviewRender.clearGenerating) AiReviewRender.clearGenerating("gen-weekly-btn");
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -75,6 +75,14 @@ def utc_window_to_bj_sql_strings(start_utc, end_utc, app_tz):
|
|||||||
return start_bj, end_bj
|
return start_bj, end_bj
|
||||||
|
|
||||||
|
|
||||||
|
def utc_window_to_utc_sql_strings(start_utc, end_utc):
|
||||||
|
"""SQLite CURRENT_TIMESTAMP 写入 UTC 时,用于 created_at 范围比较。"""
|
||||||
|
return (
|
||||||
|
start_utc.strftime("%Y-%m-%d %H:%M:%S"),
|
||||||
|
end_utc.strftime("%Y-%m-%d %H:%M:%S"),
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def normalize_bj_datetime_storage(raw):
|
def normalize_bj_datetime_storage(raw):
|
||||||
"""表单 datetime-local(含 T)入库前统一为 YYYY-MM-DD HH:MM:SS(北京时间)。"""
|
"""表单 datetime-local(含 T)入库前统一为 YYYY-MM-DD HH:MM:SS(北京时间)。"""
|
||||||
s = (raw or "").strip().replace("T", " ").replace("Z", "").strip()
|
s = (raw or "").strip().replace("T", " ").replace("Z", "").strip()
|
||||||
|
|||||||
Reference in New Issue
Block a user