feat(ai): move recommendation page to v2 shell
All checks were successful
CD Pipeline / deploy (push) Successful in 2m13s
All checks were successful
CD Pipeline / deploy (push) Successful in 2m13s
This commit is contained in:
4
app.py
4
app.py
@@ -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 防護函數
|
||||
|
||||
@@ -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 # 用於模板顯示
|
||||
|
||||
|
||||
@@ -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;">
|
||||
|
||||
@@ -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")
|
||||
|
||||
Reference in New Issue
Block a user