修复ai
This commit is contained in:
+34
-14
@@ -55,10 +55,24 @@ def _use_openai() -> bool:
|
|||||||
return _ai_provider() in ("openai", "openai_compatible", "gateway")
|
return _ai_provider() in ("openai", "openai_compatible", "gateway")
|
||||||
|
|
||||||
|
|
||||||
def _read_image_base64(image_path: str) -> Optional[str]:
|
def _image_mime_for_path(path: str) -> str:
|
||||||
|
ext = os.path.splitext(str(path or ""))[1].lower()
|
||||||
|
if ext == ".png":
|
||||||
|
return "image/png"
|
||||||
|
if ext in (".jpg", ".jpeg"):
|
||||||
|
return "image/jpeg"
|
||||||
|
if ext == ".webp":
|
||||||
|
return "image/webp"
|
||||||
|
if ext == ".gif":
|
||||||
|
return "image/gif"
|
||||||
|
return "image/jpeg"
|
||||||
|
|
||||||
|
|
||||||
|
def _read_image_base64(image_path: str) -> Optional[tuple]:
|
||||||
try:
|
try:
|
||||||
with open(image_path, "rb") as f:
|
with open(image_path, "rb") as f:
|
||||||
return base64.b64encode(f.read()).decode("utf-8")
|
b64 = base64.b64encode(f.read()).decode("utf-8")
|
||||||
|
return b64, _image_mime_for_path(image_path)
|
||||||
except Exception:
|
except Exception:
|
||||||
return None
|
return None
|
||||||
|
|
||||||
@@ -66,15 +80,15 @@ def _read_image_base64(image_path: str) -> Optional[str]:
|
|||||||
def _collect_images(
|
def _collect_images(
|
||||||
image_paths: Optional[Sequence[str]] = None,
|
image_paths: Optional[Sequence[str]] = None,
|
||||||
images_b64: Optional[Sequence[str]] = None,
|
images_b64: Optional[Sequence[str]] = None,
|
||||||
) -> List[str]:
|
) -> List[tuple]:
|
||||||
out: List[str] = []
|
out: List[tuple] = []
|
||||||
for p in image_paths or []:
|
for p in image_paths or []:
|
||||||
b = _read_image_base64(p)
|
item = _read_image_base64(p)
|
||||||
if b:
|
if item:
|
||||||
out.append(b)
|
out.append(item)
|
||||||
for b in images_b64 or []:
|
for b in images_b64 or []:
|
||||||
if b:
|
if b:
|
||||||
out.append(str(b))
|
out.append((str(b), "image/jpeg"))
|
||||||
return out
|
return out
|
||||||
|
|
||||||
|
|
||||||
@@ -85,7 +99,7 @@ def _openai_chat_url() -> str:
|
|||||||
return f"{base}/chat/completions"
|
return f"{base}/chat/completions"
|
||||||
|
|
||||||
|
|
||||||
def _generate_openai(prompt: str, images: List[str], temperature: float) -> str:
|
def _generate_openai(prompt: str, images: List[tuple], temperature: float) -> str:
|
||||||
api_key = _openai_api_key()
|
api_key = _openai_api_key()
|
||||||
if not api_key:
|
if not api_key:
|
||||||
return "AI 调用失败:未配置 OPENAI_API_KEY(请在当前实例目录 .env 中设置,修改后需重启服务)"
|
return "AI 调用失败:未配置 OPENAI_API_KEY(请在当前实例目录 .env 中设置,修改后需重启服务)"
|
||||||
@@ -95,11 +109,11 @@ def _generate_openai(prompt: str, images: List[str], temperature: float) -> str:
|
|||||||
}
|
}
|
||||||
if images:
|
if images:
|
||||||
content: List[dict] = [{"type": "text", "text": prompt}]
|
content: List[dict] = [{"type": "text", "text": prompt}]
|
||||||
for b64 in images:
|
for b64, mime in images:
|
||||||
content.append(
|
content.append(
|
||||||
{
|
{
|
||||||
"type": "image_url",
|
"type": "image_url",
|
||||||
"image_url": {"url": f"data:image/jpeg;base64,{b64}"},
|
"image_url": {"url": f"data:{mime};base64,{b64}"},
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
messages = [{"role": "user", "content": content}]
|
messages = [{"role": "user", "content": content}]
|
||||||
@@ -126,7 +140,7 @@ def _generate_openai(prompt: str, images: List[str], temperature: float) -> str:
|
|||||||
return (msg.get("content") or "").strip() or "AI 生成失败:空内容"
|
return (msg.get("content") or "").strip() or "AI 生成失败:空内容"
|
||||||
|
|
||||||
|
|
||||||
def _generate_ollama(prompt: str, images: List[str], temperature: float) -> str:
|
def _generate_ollama(prompt: str, images: List[tuple], temperature: float) -> str:
|
||||||
payload = {
|
payload = {
|
||||||
"model": _ollama_model(),
|
"model": _ollama_model(),
|
||||||
"prompt": prompt,
|
"prompt": prompt,
|
||||||
@@ -134,7 +148,7 @@ def _generate_ollama(prompt: str, images: List[str], temperature: float) -> str:
|
|||||||
"options": {"temperature": temperature},
|
"options": {"temperature": temperature},
|
||||||
}
|
}
|
||||||
if images:
|
if images:
|
||||||
payload["images"] = 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())
|
||||||
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 生成失败"
|
||||||
@@ -167,6 +181,12 @@ def ai_generate(
|
|||||||
|
|
||||||
|
|
||||||
def ai_review(trades_text: str, period_title: str, image_paths=None) -> str:
|
def ai_review(trades_text: str, period_title: str, image_paths=None) -> str:
|
||||||
|
n_img = len(image_paths or [])
|
||||||
|
attach_note = (
|
||||||
|
f"【系统说明:已向模型附带 {n_img} 张复盘附图(自动K线或上传截图),请结合附图分析第5节。】\n\n"
|
||||||
|
if n_img
|
||||||
|
else "【系统说明:本次未附带复盘附图,第5节请写明「无附图,无法看图」;保存复盘记录时可勾选「自动生成K线图」。】\n\n"
|
||||||
|
)
|
||||||
prompt = f"""
|
prompt = f"""
|
||||||
你是一位专业交易教练。下面是用户的{period_title}交易记录,请做简洁、可执行的复盘(中文)。
|
你是一位专业交易教练。下面是用户的{period_title}交易记录,请做简洁、可执行的复盘(中文)。
|
||||||
|
|
||||||
@@ -188,7 +208,7 @@ def ai_review(trades_text: str, period_title: str, image_paths=None) -> str:
|
|||||||
交易记录:
|
交易记录:
|
||||||
{trades_text}
|
{trades_text}
|
||||||
""".strip()
|
""".strip()
|
||||||
return ai_generate(prompt, image_paths=image_paths, temperature=0.2)
|
return attach_note + ai_generate(prompt, image_paths=image_paths, temperature=0.2)
|
||||||
|
|
||||||
|
|
||||||
def ai_short_advice(prompt_text: str) -> str:
|
def ai_short_advice(prompt_text: str) -> str:
|
||||||
|
|||||||
@@ -0,0 +1,110 @@
|
|||||||
|
"""AI 日复盘 / 周复盘:附图收集(各实例共用)。"""
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import os
|
||||||
|
import uuid
|
||||||
|
from typing import Callable, List, Optional, Sequence
|
||||||
|
|
||||||
|
from journal_chart_lib import (
|
||||||
|
JOURNAL_CHART_ANCHOR_CLOSE,
|
||||||
|
JOURNAL_CHART_DEFAULT_LIMIT,
|
||||||
|
JOURNAL_CHART_DEFAULT_TF1,
|
||||||
|
JOURNAL_CHART_DEFAULT_TF2,
|
||||||
|
normalize_chart_timeframe,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def collect_images_for_ai_review(
|
||||||
|
rows: Sequence,
|
||||||
|
upload_folder: str,
|
||||||
|
*,
|
||||||
|
build_chart_if_missing: Optional[Callable] = None,
|
||||||
|
) -> List[str]:
|
||||||
|
"""
|
||||||
|
收集传给视觉模型的本地图片路径。
|
||||||
|
- 优先 journal_entries.image 已存附图;
|
||||||
|
- 若无附图且提供 build_chart_if_missing,则临时生成 K 线图。
|
||||||
|
"""
|
||||||
|
paths: List[str] = []
|
||||||
|
seen = set()
|
||||||
|
upload_folder = os.path.abspath(upload_folder or "")
|
||||||
|
for row in rows or []:
|
||||||
|
candidate = None
|
||||||
|
try:
|
||||||
|
keys = row.keys() if hasattr(row, "keys") else []
|
||||||
|
except Exception:
|
||||||
|
keys = []
|
||||||
|
img = row["image"] if "image" in keys else None
|
||||||
|
if img:
|
||||||
|
candidate = os.path.join(upload_folder, str(img).strip())
|
||||||
|
elif build_chart_if_missing:
|
||||||
|
try:
|
||||||
|
candidate = build_chart_if_missing(row)
|
||||||
|
except Exception:
|
||||||
|
candidate = None
|
||||||
|
if not candidate:
|
||||||
|
continue
|
||||||
|
candidate = os.path.abspath(candidate)
|
||||||
|
if os.path.isfile(candidate) and candidate not in seen:
|
||||||
|
seen.add(candidate)
|
||||||
|
paths.append(candidate)
|
||||||
|
return paths
|
||||||
|
|
||||||
|
|
||||||
|
def build_journal_ai_chart_path(
|
||||||
|
row,
|
||||||
|
upload_folder: str,
|
||||||
|
*,
|
||||||
|
order_chart_enabled: bool,
|
||||||
|
normalize_exchange_symbol_fn: Callable[[str], str],
|
||||||
|
generate_chart_fn: Callable,
|
||||||
|
local_datetime_to_ms_fn: Callable[[str], Optional[int]],
|
||||||
|
now_ts_ms_fn: Callable[[], int],
|
||||||
|
) -> Optional[str]:
|
||||||
|
"""无已存附图时,按复盘记录开平仓时间临时生成 K 线图路径。"""
|
||||||
|
if not order_chart_enabled:
|
||||||
|
return None
|
||||||
|
try:
|
||||||
|
keys = row.keys() if hasattr(row, "keys") else []
|
||||||
|
except Exception:
|
||||||
|
return None
|
||||||
|
coin = (row["coin"] if "coin" in keys else "") or ""
|
||||||
|
coin = str(coin).strip()
|
||||||
|
if not coin:
|
||||||
|
return None
|
||||||
|
try:
|
||||||
|
symbol = normalize_exchange_symbol_fn(coin)
|
||||||
|
except Exception:
|
||||||
|
return None
|
||||||
|
open_dt = row["open_datetime"] if "open_datetime" in keys else ""
|
||||||
|
close_dt = row["close_datetime"] if "close_datetime" in keys else ""
|
||||||
|
entry_ms = local_datetime_to_ms_fn(open_dt)
|
||||||
|
exit_ms = local_datetime_to_ms_fn(close_dt)
|
||||||
|
if not entry_ms:
|
||||||
|
return None
|
||||||
|
row_tf = row["tf"] if "tf" in keys else ""
|
||||||
|
tf1 = normalize_chart_timeframe(row_tf) or JOURNAL_CHART_DEFAULT_TF1
|
||||||
|
tf2 = JOURNAL_CHART_DEFAULT_TF2 if tf1 != JOURNAL_CHART_DEFAULT_TF2 else "1h"
|
||||||
|
row_id = str(row["id"] if "id" in keys else "")[:8] or uuid.uuid4().hex[:8]
|
||||||
|
marker = {
|
||||||
|
"entry_ts_ms": entry_ms,
|
||||||
|
"exit_ts_ms": exit_ms,
|
||||||
|
"chart_anchor": JOURNAL_CHART_ANCHOR_CLOSE,
|
||||||
|
"now_ts_ms": int(now_ts_ms_fn()),
|
||||||
|
}
|
||||||
|
fname = f"ai_rev_{row_id}_{uuid.uuid4().hex[:6]}.png"
|
||||||
|
saved = generate_chart_fn(
|
||||||
|
symbol,
|
||||||
|
f"AI复盘 {coin}",
|
||||||
|
timeframes=[tf1, tf2],
|
||||||
|
limit=JOURNAL_CHART_DEFAULT_LIMIT,
|
||||||
|
out_dir=upload_folder,
|
||||||
|
filename=fname,
|
||||||
|
marker_payload=marker,
|
||||||
|
marker_timeframes={tf1, tf2},
|
||||||
|
layout="vertical",
|
||||||
|
)
|
||||||
|
if not saved:
|
||||||
|
return None
|
||||||
|
path = os.path.join(upload_folder, saved)
|
||||||
|
return path if os.path.isfile(path) else None
|
||||||
@@ -35,6 +35,7 @@ import sys
|
|||||||
if _REPO_ROOT not in sys.path:
|
if _REPO_ROOT not in sys.path:
|
||||||
sys.path.insert(0, _REPO_ROOT)
|
sys.path.insert(0, _REPO_ROOT)
|
||||||
from ai_client import ai_generate, ai_review, ai_short_advice
|
from ai_client import ai_generate, ai_review, ai_short_advice
|
||||||
|
from ai_review_lib import build_journal_ai_chart_path, collect_images_for_ai_review
|
||||||
from fib_key_monitor_lib import (
|
from fib_key_monitor_lib import (
|
||||||
FIB_KEY_MONITOR_TYPES,
|
FIB_KEY_MONITOR_TYPES,
|
||||||
calc_fib_plan,
|
calc_fib_plan,
|
||||||
@@ -7743,6 +7744,18 @@ def manual_transfer():
|
|||||||
return redirect("/")
|
return redirect("/")
|
||||||
|
|
||||||
|
|
||||||
|
def _journal_ai_chart_builder(row):
|
||||||
|
return build_journal_ai_chart_path(
|
||||||
|
row,
|
||||||
|
app.config["UPLOAD_FOLDER"],
|
||||||
|
order_chart_enabled=ORDER_CHART_ENABLED,
|
||||||
|
normalize_exchange_symbol_fn=lambda c: normalize_exchange_symbol(normalize_symbol_input(c)),
|
||||||
|
generate_chart_fn=generate_multi_timeframe_chart_png,
|
||||||
|
local_datetime_to_ms_fn=_local_input_datetime_to_ms,
|
||||||
|
now_ts_ms_fn=lambda: int(app_now().timestamp() * 1000),
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
@app.route("/ai_daily_review", methods=["POST"])
|
@app.route("/ai_daily_review", methods=["POST"])
|
||||||
@login_required
|
@login_required
|
||||||
def ai_daily_review():
|
def ai_daily_review():
|
||||||
@@ -7761,14 +7774,11 @@ def ai_daily_review():
|
|||||||
text += _journal_row_lines_for_ai(idx, row)
|
text += _journal_row_lines_for_ai(idx, row)
|
||||||
text += "\n"
|
text += "\n"
|
||||||
|
|
||||||
image_paths = []
|
image_paths = collect_images_for_ai_review(
|
||||||
for row in rows:
|
rows,
|
||||||
img = row["image"]
|
app.config["UPLOAD_FOLDER"],
|
||||||
if not img:
|
build_chart_if_missing=_journal_ai_chart_builder,
|
||||||
continue
|
)
|
||||||
img_path = os.path.join(app.config["UPLOAD_FOLDER"], img)
|
|
||||||
if os.path.exists(img_path):
|
|
||||||
image_paths.append(img_path)
|
|
||||||
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()
|
||||||
@@ -7800,14 +7810,11 @@ def ai_weekly_review():
|
|||||||
text += _journal_row_lines_for_ai(idx, row)
|
text += _journal_row_lines_for_ai(idx, row)
|
||||||
text += "\n"
|
text += "\n"
|
||||||
|
|
||||||
image_paths = []
|
image_paths = collect_images_for_ai_review(
|
||||||
for row in rows:
|
rows,
|
||||||
img = row["image"]
|
app.config["UPLOAD_FOLDER"],
|
||||||
if not img:
|
build_chart_if_missing=_journal_ai_chart_builder,
|
||||||
continue
|
)
|
||||||
img_path = os.path.join(app.config["UPLOAD_FOLDER"], img)
|
|
||||||
if os.path.exists(img_path):
|
|
||||||
image_paths.append(img_path)
|
|
||||||
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()
|
||||||
|
|||||||
+23
-16
@@ -35,6 +35,7 @@ import sys
|
|||||||
if _REPO_ROOT not in sys.path:
|
if _REPO_ROOT not in sys.path:
|
||||||
sys.path.insert(0, _REPO_ROOT)
|
sys.path.insert(0, _REPO_ROOT)
|
||||||
from ai_client import ai_generate, ai_review, ai_short_advice
|
from ai_client import ai_generate, ai_review, ai_short_advice
|
||||||
|
from ai_review_lib import build_journal_ai_chart_path, collect_images_for_ai_review
|
||||||
from fib_key_monitor_lib import (
|
from fib_key_monitor_lib import (
|
||||||
FIB_KEY_MONITOR_TYPES,
|
FIB_KEY_MONITOR_TYPES,
|
||||||
KEY_ENTRY_REASON_BY_SIGNAL,
|
KEY_ENTRY_REASON_BY_SIGNAL,
|
||||||
@@ -7832,6 +7833,18 @@ def manual_transfer():
|
|||||||
return redirect("/")
|
return redirect("/")
|
||||||
|
|
||||||
|
|
||||||
|
def _journal_ai_chart_builder(row):
|
||||||
|
return build_journal_ai_chart_path(
|
||||||
|
row,
|
||||||
|
app.config["UPLOAD_FOLDER"],
|
||||||
|
order_chart_enabled=ORDER_CHART_ENABLED,
|
||||||
|
normalize_exchange_symbol_fn=lambda c: normalize_exchange_symbol(normalize_symbol_input(c)),
|
||||||
|
generate_chart_fn=generate_multi_timeframe_chart_png,
|
||||||
|
local_datetime_to_ms_fn=_local_input_datetime_to_ms,
|
||||||
|
now_ts_ms_fn=lambda: int(app_now().timestamp() * 1000),
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
@app.route("/ai_daily_review", methods=["POST"])
|
@app.route("/ai_daily_review", methods=["POST"])
|
||||||
@login_required
|
@login_required
|
||||||
def ai_daily_review():
|
def ai_daily_review():
|
||||||
@@ -7850,14 +7863,11 @@ def ai_daily_review():
|
|||||||
text += _journal_row_lines_for_ai(idx, row)
|
text += _journal_row_lines_for_ai(idx, row)
|
||||||
text += "\n"
|
text += "\n"
|
||||||
|
|
||||||
image_paths = []
|
image_paths = collect_images_for_ai_review(
|
||||||
for row in rows:
|
rows,
|
||||||
img = row["image"]
|
app.config["UPLOAD_FOLDER"],
|
||||||
if not img:
|
build_chart_if_missing=_journal_ai_chart_builder,
|
||||||
continue
|
)
|
||||||
img_path = os.path.join(app.config["UPLOAD_FOLDER"], img)
|
|
||||||
if os.path.exists(img_path):
|
|
||||||
image_paths.append(img_path)
|
|
||||||
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()
|
||||||
@@ -7889,14 +7899,11 @@ def ai_weekly_review():
|
|||||||
text += _journal_row_lines_for_ai(idx, row)
|
text += _journal_row_lines_for_ai(idx, row)
|
||||||
text += "\n"
|
text += "\n"
|
||||||
|
|
||||||
image_paths = []
|
image_paths = collect_images_for_ai_review(
|
||||||
for row in rows:
|
rows,
|
||||||
img = row["image"]
|
app.config["UPLOAD_FOLDER"],
|
||||||
if not img:
|
build_chart_if_missing=_journal_ai_chart_builder,
|
||||||
continue
|
)
|
||||||
img_path = os.path.join(app.config["UPLOAD_FOLDER"], img)
|
|
||||||
if os.path.exists(img_path):
|
|
||||||
image_paths.append(img_path)
|
|
||||||
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()
|
||||||
|
|||||||
@@ -35,6 +35,7 @@ import sys
|
|||||||
if _REPO_ROOT not in sys.path:
|
if _REPO_ROOT not in sys.path:
|
||||||
sys.path.insert(0, _REPO_ROOT)
|
sys.path.insert(0, _REPO_ROOT)
|
||||||
from ai_client import ai_generate, ai_review, ai_short_advice
|
from ai_client import ai_generate, ai_review, ai_short_advice
|
||||||
|
from ai_review_lib import build_journal_ai_chart_path, collect_images_for_ai_review
|
||||||
from journal_chart_lib import (
|
from journal_chart_lib import (
|
||||||
JOURNAL_CHART_DEFAULT_LIMIT,
|
JOURNAL_CHART_DEFAULT_LIMIT,
|
||||||
JOURNAL_CHART_DEFAULT_TF1,
|
JOURNAL_CHART_DEFAULT_TF1,
|
||||||
@@ -7230,6 +7231,18 @@ def manual_transfer():
|
|||||||
return redirect("/")
|
return redirect("/")
|
||||||
|
|
||||||
|
|
||||||
|
def _journal_ai_chart_builder(row):
|
||||||
|
return build_journal_ai_chart_path(
|
||||||
|
row,
|
||||||
|
app.config["UPLOAD_FOLDER"],
|
||||||
|
order_chart_enabled=ORDER_CHART_ENABLED,
|
||||||
|
normalize_exchange_symbol_fn=lambda c: normalize_exchange_symbol(normalize_symbol_input(c)),
|
||||||
|
generate_chart_fn=generate_multi_timeframe_chart_png,
|
||||||
|
local_datetime_to_ms_fn=_local_input_datetime_to_ms,
|
||||||
|
now_ts_ms_fn=lambda: int(app_now().timestamp() * 1000),
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
@app.route("/ai_daily_review", methods=["POST"])
|
@app.route("/ai_daily_review", methods=["POST"])
|
||||||
@login_required
|
@login_required
|
||||||
def ai_daily_review():
|
def ai_daily_review():
|
||||||
@@ -7248,14 +7261,11 @@ def ai_daily_review():
|
|||||||
text += _journal_row_lines_for_ai(idx, row)
|
text += _journal_row_lines_for_ai(idx, row)
|
||||||
text += "\n"
|
text += "\n"
|
||||||
|
|
||||||
image_paths = []
|
image_paths = collect_images_for_ai_review(
|
||||||
for row in rows:
|
rows,
|
||||||
img = row["image"]
|
app.config["UPLOAD_FOLDER"],
|
||||||
if not img:
|
build_chart_if_missing=_journal_ai_chart_builder,
|
||||||
continue
|
)
|
||||||
img_path = os.path.join(app.config["UPLOAD_FOLDER"], img)
|
|
||||||
if os.path.exists(img_path):
|
|
||||||
image_paths.append(img_path)
|
|
||||||
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()
|
||||||
@@ -7287,14 +7297,11 @@ def ai_weekly_review():
|
|||||||
text += _journal_row_lines_for_ai(idx, row)
|
text += _journal_row_lines_for_ai(idx, row)
|
||||||
text += "\n"
|
text += "\n"
|
||||||
|
|
||||||
image_paths = []
|
image_paths = collect_images_for_ai_review(
|
||||||
for row in rows:
|
rows,
|
||||||
img = row["image"]
|
app.config["UPLOAD_FOLDER"],
|
||||||
if not img:
|
build_chart_if_missing=_journal_ai_chart_builder,
|
||||||
continue
|
)
|
||||||
img_path = os.path.join(app.config["UPLOAD_FOLDER"], img)
|
|
||||||
if os.path.exists(img_path):
|
|
||||||
image_paths.append(img_path)
|
|
||||||
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()
|
||||||
|
|||||||
+23
-16
@@ -35,6 +35,7 @@ import sys
|
|||||||
if _REPO_ROOT not in sys.path:
|
if _REPO_ROOT not in sys.path:
|
||||||
sys.path.insert(0, _REPO_ROOT)
|
sys.path.insert(0, _REPO_ROOT)
|
||||||
from ai_client import ai_generate, ai_review, ai_short_advice
|
from ai_client import ai_generate, ai_review, ai_short_advice
|
||||||
|
from ai_review_lib import build_journal_ai_chart_path, collect_images_for_ai_review
|
||||||
from fib_key_monitor_lib import (
|
from fib_key_monitor_lib import (
|
||||||
FIB_KEY_MONITOR_TYPES,
|
FIB_KEY_MONITOR_TYPES,
|
||||||
calc_fib_plan,
|
calc_fib_plan,
|
||||||
@@ -7138,6 +7139,18 @@ def manual_transfer():
|
|||||||
return redirect("/")
|
return redirect("/")
|
||||||
|
|
||||||
|
|
||||||
|
def _journal_ai_chart_builder(row):
|
||||||
|
return build_journal_ai_chart_path(
|
||||||
|
row,
|
||||||
|
app.config["UPLOAD_FOLDER"],
|
||||||
|
order_chart_enabled=ORDER_CHART_ENABLED,
|
||||||
|
normalize_exchange_symbol_fn=lambda c: normalize_exchange_symbol(normalize_symbol_input(c)),
|
||||||
|
generate_chart_fn=generate_multi_timeframe_chart_png,
|
||||||
|
local_datetime_to_ms_fn=_local_input_datetime_to_ms,
|
||||||
|
now_ts_ms_fn=lambda: int(app_now().timestamp() * 1000),
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
@app.route("/ai_daily_review", methods=["POST"])
|
@app.route("/ai_daily_review", methods=["POST"])
|
||||||
@login_required
|
@login_required
|
||||||
def ai_daily_review():
|
def ai_daily_review():
|
||||||
@@ -7163,14 +7176,11 @@ def ai_daily_review():
|
|||||||
f" 问题:{issues}\n\n"
|
f" 问题:{issues}\n\n"
|
||||||
)
|
)
|
||||||
|
|
||||||
image_paths = []
|
image_paths = collect_images_for_ai_review(
|
||||||
for row in rows:
|
rows,
|
||||||
img = row["image"]
|
app.config["UPLOAD_FOLDER"],
|
||||||
if not img:
|
build_chart_if_missing=_journal_ai_chart_builder,
|
||||||
continue
|
)
|
||||||
img_path = os.path.join(app.config["UPLOAD_FOLDER"], img)
|
|
||||||
if os.path.exists(img_path):
|
|
||||||
image_paths.append(img_path)
|
|
||||||
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()
|
||||||
@@ -7208,14 +7218,11 @@ def ai_weekly_review():
|
|||||||
f" 心态标签:{issues} | 持仓:{row['hold_duration']}\n\n"
|
f" 心态标签:{issues} | 持仓:{row['hold_duration']}\n\n"
|
||||||
)
|
)
|
||||||
|
|
||||||
image_paths = []
|
image_paths = collect_images_for_ai_review(
|
||||||
for row in rows:
|
rows,
|
||||||
img = row["image"]
|
app.config["UPLOAD_FOLDER"],
|
||||||
if not img:
|
build_chart_if_missing=_journal_ai_chart_builder,
|
||||||
continue
|
)
|
||||||
img_path = os.path.join(app.config["UPLOAD_FOLDER"], img)
|
|
||||||
if os.path.exists(img_path):
|
|
||||||
image_paths.append(img_path)
|
|
||||||
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()
|
||||||
|
|||||||
Reference in New Issue
Block a user