feat(ai): move recommendation page to v2 shell
All checks were successful
CD Pipeline / deploy (push) Successful in 2m13s

This commit is contained in:
OoO
2026-05-01 21:08:44 +08:00
parent 9b3e0a4565
commit d6782ee710
4 changed files with 134 additions and 14 deletions

4
app.py
View File

@@ -95,8 +95,8 @@ except Exception as e:
sys_log.error(f"無法檢測磁碟空間: {e}")
# 🚩 系統版本定義 (備份與顯示用)
# 🚩 2026-05-01 V10.74: Move AI history page onto V2 shell
SYSTEM_VERSION = "V10.74"
# 🚩 2026-05-01 V10.75: Move AI recommendation page onto V2 shell
SYSTEM_VERSION = "V10.75"
# ==========================================
# 🔒 SQL Injection 防護函數

View File

@@ -254,7 +254,7 @@ YOUTUBE_API_KEY = os.getenv('YOUTUBE_API_KEY', '')
# ==========================================
# 系統版本與路徑
# ==========================================
SYSTEM_VERSION = "V10.74"
SYSTEM_VERSION = "V10.75"
LOG_FILE_PATH = os.path.join(BASE_DIR, 'logs/system.log')
public_url = PUBLIC_URL # 用於模板顯示

View File

@@ -1,17 +1,111 @@
{% extends 'base.html' %}
{% extends 'ewoooc_base.html' %}
{% block title %}AI 智慧推薦 - WOOO TECH{% endblock %}
{% block content %}
<div class="container-fluid py-3">
{% block extra_css %}
<style>
.ai-recommend-page {
display: flex;
flex-direction: column;
gap: 18px;
}
.ai-recommend-hero {
padding: 20px;
border: 1px solid var(--momo-border-strong);
border-radius: 8px;
background:
radial-gradient(circle at 18px 18px, rgba(42, 37, 32, 0.12) 1px, transparent 1px),
linear-gradient(135deg, rgba(242, 178, 90, 0.22), rgba(255, 255, 255, 0.94) 46%, rgba(42, 37, 32, 0.06));
background-size: 18px 18px, auto;
box-shadow: var(--momo-shadow-soft);
}
.ai-recommend-title {
display: flex;
align-items: center;
gap: 10px;
margin: 0;
color: var(--momo-text-strong);
font-family: var(--momo-font-display);
font-size: clamp(1.35rem, 2vw, 2rem);
font-weight: 800;
letter-spacing: 0;
}
.ai-recommend-title i {
color: var(--momo-warm-caramel) !important;
}
.ai-recommend-page .card {
border: 1px solid var(--momo-border-subtle) !important;
border-radius: 8px;
background: rgba(255, 255, 255, 0.84);
box-shadow: var(--momo-shadow-soft);
}
.ai-recommend-page .card-header {
border-color: var(--momo-border-subtle) !important;
background: rgba(250, 247, 240, 0.88) !important;
color: var(--momo-text-strong) !important;
}
.ai-recommend-page .card-header h6,
.ai-recommend-page .card-header span {
color: var(--momo-text-strong);
font-family: var(--momo-font-display);
font-weight: 800;
}
.ai-recommend-page .form-control,
.ai-recommend-page .form-select,
.ai-recommend-page .input-group-text {
border-color: var(--momo-border-subtle);
border-radius: 8px;
}
.ai-recommend-page .btn-primary {
background: var(--momo-text-strong);
border-color: var(--momo-text-strong);
font-weight: 800;
}
.ai-recommend-page .btn-outline-primary {
color: var(--momo-accent-strong);
border-color: rgba(42, 37, 32, 0.24);
}
.ai-recommend-page .badge {
border-radius: 999px;
font-weight: 800;
}
.ai-recommend-page .bg-light {
background-color: rgba(250, 247, 240, 0.76) !important;
}
.ai-recommend-page .alert-info {
border-color: rgba(83, 135, 154, 0.2);
background: rgba(238, 248, 251, 0.82);
}
.ai-recommend-page #marketInfoTabs .nav-link.active {
background: var(--momo-text-strong) !important;
color: #fff !important;
}
</style>
{% endblock %}
{% block ewooo_content %}
<div class="ai-recommend-page">
<!-- 頁面標題 - 更緊湊 -->
<div class="row mb-3">
<div class="row mb-3 ai-recommend-hero">
<div class="col-12">
<div class="d-flex justify-content-between align-items-center flex-wrap gap-2">
<div>
<h4 class="mb-0">
<h1 class="ai-recommend-title">
<i class="fas fa-robot text-primary me-2"></i>AI 智慧推薦
</h4>
<small class="text-muted">根據市場趨勢,智慧生成銷售文案</small>
</h1>
<small class="text-muted">根據資料庫商品分類、即時趨勢與 AI 生成紀錄,產出可追蹤的銷售文案</small>
</div>
<div class="d-flex align-items-center gap-2">
<span id="ollamaStatus" class="badge {% if ollama_status %}bg-success{% else %}bg-secondary{% endif %}">
@@ -200,10 +294,9 @@
</div>
<!-- 快速搜尋標籤 -->
<div class="mb-2">
<span class="badge bg-light text-dark border me-1" style="cursor: pointer; font-size: 0.7rem;" onclick="quickWebSearch('保濕面膜')">保濕面膜</span>
<span class="badge bg-light text-dark border me-1" style="cursor: pointer; font-size: 0.7rem;" onclick="quickWebSearch('美白精華')">美白精華</span>
<span class="badge bg-light text-dark border me-1" style="cursor: pointer; font-size: 0.7rem;" onclick="quickWebSearch('防曬乳')">防曬乳</span>
<span class="badge bg-light text-dark border me-1" style="cursor: pointer; font-size: 0.7rem;" onclick="quickWebSearch('抗老保養')">抗老保養</span>
{% for category in product_categories[:4] %}
<span class="badge bg-light text-dark border me-1" style="cursor: pointer; font-size: 0.7rem;" onclick="quickWebSearch({{ category|tojson }})">{{ category }}</span>
{% endfor %}
</div>
<!-- 搜尋結果區 - 不限高度,完整顯示 -->
<div id="webSearchResult" style="display: none;">

View File

@@ -201,6 +201,33 @@ def test_ai_history_uses_v2_shell_and_real_history_apis():
assert "ai_history_service.get_statistics" in route_source
def test_ai_recommend_uses_v2_shell_and_runtime_category_data():
template = (ROOT / "templates/ai_recommend.html").read_text(encoding="utf-8")
route_source = (ROOT / "routes/ai_routes.py").read_text(encoding="utf-8")
assert "{% extends 'ewoooc_base.html' %}" in template
assert "{% block ewooo_content %}" in template
assert "{% block extra_css %}" in template
assert "ai-recommend-hero" in template
assert "ai-recommend-page" in template
assert "{% for category in product_categories[:4] %}" in template
assert "quickWebSearch({{ category|tojson }})" in template
assert "quickWebSearch('保濕面膜')" not in template
assert "fetch('/api/ai/generate_copy'" in template
assert "fetch('/api/ai/web_search'" in template
assert "fetch('/api/ai/product_insights'" in template
assert "fetch('/api/ai/gemini_usage?days=30')" in template
assert "mock" not in template.lower()
assert "假商品" not in template
assert "@ai_bp.route('/ai_recommend')" in route_source
assert "render_template('ai_recommend.html'" in route_source
assert "product_categories=product_categories" in route_source
assert "@ai_bp.route('/api/ai/generate_copy'" in route_source
assert "@ai_bp.route('/api/ai/web_search'" in route_source
assert "@ai_bp.route('/api/ai/product_insights'" in route_source
def test_dashboard_v2_restores_real_price_history_chart():
route_source = (ROOT / "routes/api_routes.py").read_text(encoding="utf-8")
dashboard = (ROOT / "templates/dashboard_v2.html").read_text(encoding="utf-8")