diff --git a/.env.example b/.env.example index a9bec7e..eac74e8 100644 --- a/.env.example +++ b/.env.example @@ -47,6 +47,14 @@ EMAIL_RECEIVER=receiver_email@gmail.com PUBLIC_URL=http://your_server_ip:port NGROK_AUTH_TOKEN=your_ngrok_auth_token +# ========================================== +# 市場情報模組設定(預設全部關閉) +# ========================================== +# Phase 1 僅允許安全骨架;正式爬蟲與 DB 寫入需逐步開啟 +MARKET_INTEL_ENABLED=false +MARKET_INTEL_CRAWLER_ENABLED=false +MARKET_INTEL_WRITE_ENABLED=false + # ========================================== # 通訊模組設定(從環境變數讀取) # ========================================== diff --git a/TODO_NEXT_STEPS.txt b/TODO_NEXT_STEPS.txt index 115ee9c..f27cfdb 100644 --- a/TODO_NEXT_STEPS.txt +++ b/TODO_NEXT_STEPS.txt @@ -1,4 +1,22 @@ +================================================================================ + 跨平台市場情報模組 (ADR-035 / 2026-05-06) [IN PROGRESS] +================================================================================ + +【已完成】 + - ADR-035:定義跨平台市場活動情報系統、feature flags、market_* schema、爬蟲安全邊界與分階段 rollout。 + - 模組化盤點:更新 `docs/memory/code_modularization_inventory_20260430.md`,標記市場情報不可塞回 `app.py`、`scheduler.py`、`routes/sales_routes.py` 等大檔。 + - Phase 1 安全骨架:新增 `routes/market_intel_routes.py`、`services/market_intel/`、`templates/market_intel/disabled.html`,預設只顯示 disabled 狀態。 + - Phase 2 schema 骨架:新增 `database/market_intel_models.py`,定義 `market_platforms`、`market_campaigns`、`market_campaign_snapshots`、`market_campaign_products`、`market_product_price_history`、`market_product_matches`、`market_crawler_runs`。 + - Metadata 守門:`database/manager.py` 顯式 import market_intel models,`app.py` expected metadata tables 已同步。 + - 安全開關:`.env.example` 補 `MARKET_INTEL_ENABLED=false`、`MARKET_INTEL_CRAWLER_ENABLED=false`、`MARKET_INTEL_WRITE_ENABLED=false`。 + +【下次待辦】 + - 補 marketplace 平台種子資料策略,但正式寫入仍需 feature flag 與 migration/smoke gate。 + - 新增 `services/market_intel/adapters/base.py` 與第一個 read-only MOMO/PChome discovery adapter。 + - 新增 market_intel schema smoke test,確認 fresh env metadata 表名一致。 + - 市場情報 UI 後續頁面必須沿用 V2 暖紙、暖墨、等寬數字與點陣風格,禁止複製巨型分析頁 template 模式。 + ================================================================================ AI 自動化閉環治理同步 (2026-04-29) [DONE] ================================================================================ diff --git a/app.py b/app.py index 7f811ce..1c6b68a 100644 --- a/app.py +++ b/app.py @@ -96,7 +96,7 @@ except Exception as e: # 🚩 系統版本定義 (備份與顯示用) # 🚩 2026-05-01 V10.76: Move monthly analysis report onto V2 shell -SYSTEM_VERSION = "V10.77" +SYSTEM_VERSION = "V10.86" # ========================================== # 🔒 SQL Injection 防護函數 @@ -378,6 +378,10 @@ from routes.pchome_routes import pchome_bp app.register_blueprint(pchome_bp) sys_log.info("[Blueprint] ✅ pchome_bp 已註冊") +from routes.market_intel_routes import market_intel_bp +app.register_blueprint(market_intel_bp) +sys_log.info("[Blueprint] ✅ market_intel_bp 已註冊") + # V-Fix: 註冊 slugify 函數供模板使用(實作搬至 utils/text_helpers.py) from utils.text_helpers import slugify # noqa: E402 @@ -398,6 +402,9 @@ EXPECTED_METADATA_TABLES = { 'import_jobs', 'import_config', 'notification_templates', 'ppt_reports', 'vendor_stockout', 'vendor_list', 'vendor_emails', 'email_send_log', 'realtime_sales_monthly', + 'market_platforms', 'market_campaigns', 'market_campaign_snapshots', + 'market_campaign_products', 'market_product_price_history', + 'market_product_matches', 'market_crawler_runs', } diff --git a/config.py b/config.py index 1c0b1f7..6a548c1 100644 --- a/config.py +++ b/config.py @@ -111,6 +111,13 @@ EMAIL_RECEIVER = os.getenv('EMAIL_RECEIVER', '') # ========================================== PUBLIC_URL = os.getenv('PUBLIC_URL', 'https://mo.wooo.work') +# ========================================== +# 市場情報模組設定(預設全部關閉) +# ========================================== +MARKET_INTEL_ENABLED = os.getenv('MARKET_INTEL_ENABLED', 'false').lower() == 'true' +MARKET_INTEL_CRAWLER_ENABLED = os.getenv('MARKET_INTEL_CRAWLER_ENABLED', 'false').lower() == 'true' +MARKET_INTEL_WRITE_ENABLED = os.getenv('MARKET_INTEL_WRITE_ENABLED', 'false').lower() == 'true' + # 補上 EXCEL_EXPORT_DIR 定義 EXCEL_EXPORT_DIR = os.path.join(DATA_DIR, 'excel_exports') @@ -307,7 +314,7 @@ YOUTUBE_API_KEY = os.getenv('YOUTUBE_API_KEY', '') # ========================================== # 系統版本與路徑 # ========================================== -SYSTEM_VERSION = "V10.76" +SYSTEM_VERSION = "V10.86" LOG_FILE_PATH = os.path.join(BASE_DIR, 'logs/system.log') public_url = PUBLIC_URL # 用於模板顯示 diff --git a/database/manager.py b/database/manager.py index aef6d73..182180f 100644 --- a/database/manager.py +++ b/database/manager.py @@ -25,6 +25,15 @@ from .notification_models import NotificationTemplate # noqa: F401 - 確保 not from .ppt_reports import PPTReport # noqa: F401 - 確保 ppt_reports 表被 Base.metadata 管理 from .vendor_models import VendorStockout, VendorList, VendorEmail, EmailSendLog # noqa: F401 - 確保 vendor 表被 Base.metadata 管理 from .realtime_sales_models import RealtimeSalesMonthly # noqa: F401 - 確保 realtime_sales_monthly 被 Base.metadata 管理 +from .market_intel_models import ( # noqa: F401 - ADR-035 market_* 表 + MarketPlatform, + MarketCampaign, + MarketCampaignSnapshot, + MarketCampaignProduct, + MarketProductPriceHistory, + MarketProductMatch, + MarketCrawlerRun, +) # 🚩 導入優化後的日誌管理模組 from utils.logger_manager import SystemLogger diff --git a/database/market_intel_models.py b/database/market_intel_models.py new file mode 100644 index 0000000..d13ad9d --- /dev/null +++ b/database/market_intel_models.py @@ -0,0 +1,224 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +"""跨平台市場活動情報 ORM models。""" + +from datetime import datetime, timedelta, timezone + +from sqlalchemy import ( + Boolean, + Column, + DateTime, + Float, + ForeignKey, + Index, + Integer, + String, + Text, + UniqueConstraint, +) +from sqlalchemy.orm import relationship + +from database.models import Base + + +TAIPEI_TZ = timezone(timedelta(hours=8)) + + +def taipei_now(): + """取得台北時間 naive datetime,符合專案 DB 時間規範。""" + return datetime.now(TAIPEI_TZ).replace(tzinfo=None) + + +class MarketPlatform(Base): + """市場平台設定,例如 MOMO / PChome / Coupang / Shopee。""" + + __tablename__ = "market_platforms" + + id = Column(Integer, primary_key=True, autoincrement=True) + code = Column(String(50), unique=True, nullable=False, index=True) + name = Column(String(120), nullable=False) + base_url = Column(String(500)) + enabled = Column(Boolean, default=False, nullable=False) + crawl_policy_json = Column(Text) + created_at = Column(DateTime, default=taipei_now, nullable=False) + updated_at = Column(DateTime, default=taipei_now, onupdate=taipei_now, nullable=False) + + campaigns = relationship("MarketCampaign", back_populates="platform") + + +class MarketCampaign(Base): + """跨平台活動檔期。""" + + __tablename__ = "market_campaigns" + + id = Column(Integer, primary_key=True, autoincrement=True) + platform_code = Column(String(50), ForeignKey("market_platforms.code"), nullable=False, index=True) + campaign_key = Column(String(200), nullable=False) + campaign_name = Column(String(500), nullable=False) + campaign_type = Column(String(80), index=True) + campaign_url = Column(Text) + start_at = Column(DateTime) + end_at = Column(DateTime) + status = Column(String(30), default="unknown", nullable=False, index=True) + discovered_at = Column(DateTime, default=taipei_now, nullable=False) + last_seen_at = Column(DateTime, default=taipei_now, nullable=False) + metadata_json = Column(Text) + + platform = relationship("MarketPlatform", back_populates="campaigns") + snapshots = relationship("MarketCampaignSnapshot", back_populates="campaign") + products = relationship("MarketCampaignProduct", back_populates="campaign") + + __table_args__ = ( + UniqueConstraint("platform_code", "campaign_key", name="uq_market_campaign_platform_key"), + Index("idx_market_campaign_status_time", "status", "start_at", "end_at"), + ) + + +class MarketCampaignSnapshot(Base): + """活動頁每次爬取快照。""" + + __tablename__ = "market_campaign_snapshots" + + id = Column(Integer, primary_key=True, autoincrement=True) + campaign_id = Column(Integer, ForeignKey("market_campaigns.id"), nullable=False, index=True) + batch_id = Column(String(80), nullable=False, index=True) + crawled_at = Column(DateTime, default=taipei_now, nullable=False, index=True) + title = Column(String(500)) + hero_text = Column(Text) + coupon_text = Column(Text) + raw_discount_text = Column(Text) + page_hash = Column(String(128), index=True) + raw_snapshot_path = Column(Text) + status = Column(String(30), default="success", nullable=False, index=True) + error_message = Column(Text) + metadata_json = Column(Text) + + campaign = relationship("MarketCampaign", back_populates="snapshots") + + __table_args__ = ( + Index("idx_market_campaign_snapshot_campaign_time", "campaign_id", "crawled_at"), + ) + + +class MarketCampaignProduct(Base): + """活動頁中的平台商品快照主檔。""" + + __tablename__ = "market_campaign_products" + + id = Column(Integer, primary_key=True, autoincrement=True) + campaign_id = Column(Integer, ForeignKey("market_campaigns.id"), nullable=False, index=True) + platform_code = Column(String(50), nullable=False, index=True) + platform_product_id = Column(String(200), nullable=False, index=True) + product_url = Column(Text) + name = Column(String(500), nullable=False) + brand = Column(String(200), index=True) + image_url = Column(Text) + category_text = Column(String(300), index=True) + price = Column(Float) + original_price = Column(Float) + discount_text = Column(String(200)) + discount_rate = Column(Float) + coupon_text = Column(Text) + stock_text = Column(String(200)) + sold_count = Column(Integer) + rating = Column(Float) + review_count = Column(Integer) + rank_position = Column(Integer) + is_active = Column(Boolean, default=True, nullable=False, index=True) + first_seen_at = Column(DateTime, default=taipei_now, nullable=False) + last_seen_at = Column(DateTime, default=taipei_now, nullable=False, index=True) + metadata_json = Column(Text) + + campaign = relationship("MarketCampaign", back_populates="products") + price_history = relationship("MarketProductPriceHistory", back_populates="market_product") + matches = relationship("MarketProductMatch", back_populates="market_product") + + __table_args__ = ( + UniqueConstraint( + "campaign_id", + "platform_code", + "platform_product_id", + name="uq_market_campaign_product", + ), + Index("idx_market_product_platform_seen", "platform_code", "last_seen_at"), + Index("idx_market_product_discount", "discount_rate", "price"), + ) + + +class MarketProductPriceHistory(Base): + """市場商品價格歷史快照。""" + + __tablename__ = "market_product_price_history" + + id = Column(Integer, primary_key=True, autoincrement=True) + market_product_id = Column(Integer, ForeignKey("market_campaign_products.id"), nullable=False, index=True) + campaign_id = Column(Integer, ForeignKey("market_campaigns.id"), nullable=False, index=True) + platform_code = Column(String(50), nullable=False, index=True) + platform_product_id = Column(String(200), nullable=False, index=True) + price = Column(Float) + original_price = Column(Float) + discount_rate = Column(Float) + stock_text = Column(String(200)) + sold_count = Column(Integer) + rank_position = Column(Integer) + crawled_at = Column(DateTime, default=taipei_now, nullable=False, index=True) + batch_id = Column(String(80), nullable=False, index=True) + metadata_json = Column(Text) + + market_product = relationship("MarketCampaignProduct", back_populates="price_history") + + __table_args__ = ( + Index("idx_market_price_platform_time", "platform_code", "platform_product_id", "crawled_at"), + Index("idx_market_price_campaign_time", "campaign_id", "crawled_at"), + ) + + +class MarketProductMatch(Base): + """市場商品與我方 MOMO 商品的比對審核結果。""" + + __tablename__ = "market_product_matches" + + id = Column(Integer, primary_key=True, autoincrement=True) + market_product_id = Column(Integer, ForeignKey("market_campaign_products.id"), nullable=False, index=True) + momo_product_id = Column(Integer, ForeignKey("products.id"), index=True) + momo_i_code = Column(String(50), index=True) + match_score = Column(Float, default=0.0, nullable=False) + match_status = Column(String(30), default="needs_review", nullable=False, index=True) + match_reason_json = Column(Text) + created_at = Column(DateTime, default=taipei_now, nullable=False) + reviewed_at = Column(DateTime) + reviewed_by = Column(String(120)) + + market_product = relationship("MarketCampaignProduct", back_populates="matches") + + __table_args__ = ( + UniqueConstraint("market_product_id", "momo_i_code", name="uq_market_product_momo_match"), + Index("idx_market_match_status_score", "match_status", "match_score"), + ) + + +class MarketCrawlerRun(Base): + """市場情報爬蟲執行紀錄。""" + + __tablename__ = "market_crawler_runs" + + id = Column(Integer, primary_key=True, autoincrement=True) + platform_code = Column(String(50), index=True) + crawler_name = Column(String(120), nullable=False, index=True) + campaign_id = Column(Integer, ForeignKey("market_campaigns.id"), index=True) + batch_id = Column(String(80), nullable=False, index=True) + started_at = Column(DateTime, default=taipei_now, nullable=False, index=True) + finished_at = Column(DateTime) + status = Column(String(30), default="started", nullable=False, index=True) + dry_run = Column(Boolean, default=True, nullable=False) + pages_found = Column(Integer, default=0, nullable=False) + products_found = Column(Integer, default=0, nullable=False) + products_changed = Column(Integer, default=0, nullable=False) + error_count = Column(Integer, default=0, nullable=False) + error_message = Column(Text) + metadata_json = Column(Text) + + __table_args__ = ( + Index("idx_market_crawler_run_platform_time", "platform_code", "started_at"), + Index("idx_market_crawler_run_status_time", "status", "started_at"), + ) diff --git a/docs/adr/ADR-035-cross-platform-market-campaign-intelligence.md b/docs/adr/ADR-035-cross-platform-market-campaign-intelligence.md new file mode 100644 index 0000000..21db785 --- /dev/null +++ b/docs/adr/ADR-035-cross-platform-market-campaign-intelligence.md @@ -0,0 +1,181 @@ +# ADR-035: 跨平台市場活動情報系統 + +- **狀態**: Accepted +- **日期**: 2026-05-06 +- **觸發**: 使用者提出定期掌握 MOMO / 蝦皮 / 酷澎 / PChome 等電商活動檔期、活動商品、競品價格與資料庫保存需求 +- **相關 ADR**: ADR-011(跨專案資源隔離)、ADR-017(模組化收尾路線圖)、ADR-025(市場情報週報) +- **相關 Memory**: `docs/memory/code_modularization_inventory_20260430.md` + +## Context + +EwoooC 目前已有 MOMO EDM / 節慶活動資料、`promo_products`、PChome 競品價格 feeder、PPT 市場情報週報與多個分析頁,但跨平台活動情報尚未形成獨立資料模型與可維護爬蟲框架。 + +若直接把蝦皮、酷澎、PChome 等平台資料塞進既有 `promo_products`、`routes/sales_routes.py`、`scheduler.py` 或分析頁大型 template,會造成下列風險: + +1. 不同平台欄位語意不同,舊表無法承載活動檔期、平台商品 ID、折扣券、銷售訊號與價格歷史。 +2. 既有 `scheduler.py`、`routes/sales_routes.py`、`routes/dashboard_routes.py`、多個 crawler service 已達大檔治理門檻,新增功能會加劇技術債。 +3. 新爬蟲若直接上正式排程,可能造成 DB 寫入量、外部平台 rate limit、排程阻塞與告警噪音。 +4. 新市場情報頁若複製既有巨型 Jinja template 模式,會延續 UI token、字體、色彩與點陣風格不一致問題。 + +## Decision + +建立新的 **跨平台市場活動情報系統**,採分階段落地、預設關閉、可獨立回退的方式推進。 + +### 1. 模組邊界 + +新功能必須使用獨立模組,不得塞回既有巨檔: + +- `services/market_intel/`:活動探索、商品爬取、正規化、商品比對、告警摘要。 +- `services/market_intel/adapters/`:各平台 adapter,例如 MOMO / PChome / Coupang / Shopee。 +- `database/market_intel_models.py`:`market_*` ORM models。 +- `routes/market_intel_routes.py`:市場情報頁面與 API route glue。 +- `templates/market_intel/`:市場情報 UI template。 +- `services/scheduler/` 或獨立 job module:市場情報排程掛載點。 + +### 2. 資料模型 + +新增 `market_*` schema 作為唯一主資料層: + +- `market_platforms` +- `market_campaigns` +- `market_campaign_snapshots` +- `market_campaign_products` +- `market_product_price_history` +- `market_product_matches` +- `market_crawler_runs` + +`promo_products` 只作為既有 MOMO 活動資料的相容來源或雙寫過渡,不再承接跨平台唯一真相。 + +### 3. Feature Flag 與啟用策略 + +市場情報第一階段必須預設關閉: + +- `MARKET_INTEL_ENABLED=false` +- `MARKET_INTEL_CRAWLER_ENABLED=false` +- `MARKET_INTEL_WRITE_ENABLED=false` + +初期允許 dry-run:建立 run log、解析活動與商品,但不大量寫入正式商品資料。正式入庫與排程啟用需另行通過 smoke test、rate limit 驗證與 rollback drill。 + +### 4. 爬蟲安全邊界 + +市場情報爬蟲只允許抓公開頁面或公開結構化資料,不做登入、不碰會員資料、不破解反爬、不使用帳號池、不繞付費牆。 + +每平台 adapter 必須具備: + +- rate limit +- timeout +- retry ceiling +- user-agent 與來源識別 +- run log +- error classification +- dry-run mode +- 可單平台停用開關 + +### 5. UI / UX 邊界 + +市場情報 UI 不複製巨型分析頁模式,必須先抽共用元件與設計 token: + +- 活動檔期看板 +- 活動商品池 +- 商品比對審核 +- 市場機會與威脅 + +所有新頁必須符合 V2 暖紙、暖墨、焦糖 accent、等寬數字、點陣紋理與真實資料規範。未串接資料時只能顯示可診斷空狀態,不得使用假商品或假 KPI。 + +### 6. 上線與回退 + +部署遵守 ADR-011: + +- 禁止 `docker compose ... --remove-orphans` +- 禁止影響 `momo-db` 容器生命週期 +- 只用 `docker compose up -d --no-deps --force-recreate ` 精準重建 +- health check 只打 `/health` + +異常回退順序: + +1. 關閉 `MARKET_INTEL_*` feature flags。 +2. 停用市場情報 scheduler job。 +3. 保留 `market_*` 表與 run log,不刪資料。 +4. 回復上一版 route / service 程式碼。 +5. 僅重啟受影響應用容器,不動 DB。 + +## Phased Rollout + +### Phase 0:Readiness Audit + +- 更新模組化 inventory。 +- 確認 `scheduler.py` 無 conflict marker 且可 py_compile。 +- 標記市場情報不可寫入的既有大檔。 +- 完成 ADR-035。 + +### Phase 1:Skeleton Only + +- 新增 feature flags。 +- 新增 `market_intel` package skeleton。 +- 新增空 route / disabled page / dry-run service。 +- 不啟用正式排程,不大量入庫。 + +### Phase 2:DB Schema + +- 新增 `market_*` ORM models。 +- 補 metadata import 與 schema smoke。 +- 寫入 crawler run log 與少量 dry-run snapshot。 + +### Phase 3:MOMO / PChome Adapter + +- 先接成本最低且已有脈絡的平台。 +- 只抓公開活動入口與活動商品。 +- 建立活動與商品正規化規則。 + +### Phase 4:Coupang / Shopee Adapter + +- Coupang 先做保守 adapter。 +- Shopee 因動態資料與反爬風險較高,最後做,並維持更嚴格節流。 + +### Phase 5:Product Matching + HITL + +- 用品牌、規格、容量、關鍵字與現有商品資料計算 match score。 +- 低信心進人工審核,不自動合併。 + +### Phase 6:AI Insight / Telegram + +- 只基於 DB 實證資料產生摘要與告警。 +- 不做空泛 LLM 建議。 +- 高風險告警需包含平台、活動、商品、價差、資料時間與可追溯 run id。 + +## Alternatives Considered + +### 方案 A:直接擴充 `promo_products` + +不採用。`promo_products` 偏 MOMO 活動商品語境,無法乾淨承載跨平台活動、商品快照、價格歷史、比對審核與 crawler run log。 + +### 方案 B:直接塞進 `scheduler.py` 與既有 crawler service + +不採用。`scheduler.py` 與多個 crawler service 已達大檔治理門檻,新增跨平台 adapter 會讓排程與錯誤隔離更脆弱。 + +### 方案 C:先做完整 UI 再補資料 + +不採用。違反真實資料與真實頁面規範,容易產生假 KPI、假商品與不可診斷狀態。 + +## Consequences + +**正面** + +- 市場情報可以獨立演進,不污染業績分析、dashboard、scheduler 既有技術債。 +- 每平台 adapter 可單獨停用、測試與回退。 +- `market_*` schema 可保留歷史、做趨勢與商品比對。 +- AI 告警能基於可追溯 DB run log,降低空泛推論。 + +**負面** + +- 初期開發量比「直接塞舊表」更高。 +- 需要新增 schema、service、route、UI 與 scheduler registry。 +- Shopee 等平台可能因公開資料穩定性與 rate limit 需要較長探索期。 + +## Acceptance Criteria + +- Phase 0 完成後,不改 runtime 行為。 +- Phase 1 完成後,`MARKET_INTEL_ENABLED=false` 時所有新功能完全不影響既有頁面與排程。 +- 任一 crawler adapter 失敗不得阻塞既有 MOMO 排程。 +- 新市場情報 route 不新增到 `app.py`。 +- 任一新 Python 檔若超過 600 行需提出拆分理由,超過 800 行需更新模組化 inventory。 diff --git a/docs/adr/README.md b/docs/adr/README.md index 80fe8c8..33c0f4c 100644 --- a/docs/adr/README.md +++ b/docs/adr/README.md @@ -56,6 +56,7 @@ | [032](ADR-032-rag-autonomous-learning-loop.md) | RAG 自主學習迴圈 — Distiller + PromotionGate + 反饋環(Phase 11) | Accepted | 2026-05-03 | | [033](ADR-033-rag-three-guardrails.md) | RAG 治理三護欄 — Promotion Gate / Firecrawl 資源 / BGE-M3 一致性(Owen v5.0 鐵律) | Accepted | 2026-05-03 | | [034](ADR-034-dynamic-model-router.md) | Caller × Context 動態 Model Router(短文 gemma3 / 複雜 SKU qwen3:14b / 重構 coder:32b) | Accepted | 2026-05-04 | +| [035](ADR-035-cross-platform-market-campaign-intelligence.md) | 跨平台市場活動情報系統 | Accepted | 2026-05-06 | ## 規範 diff --git a/docs/guides/claude_design_brief.md b/docs/guides/claude_design_brief.md new file mode 100644 index 0000000..52eaa25 --- /dev/null +++ b/docs/guides/claude_design_brief.md @@ -0,0 +1,581 @@ +# EwoooC × MOMO Pro — Claude Design Brief +**Version:** 1.0 (2026-05-01) +**Purpose:** 供 Claude Design 理解現況並繼續 UI/UX 視覺設計優化。 + +--- + +## 1. 專案概覽 + +| 欄位 | 內容 | +|------|------| +| 產品名稱 | EwoooC 商家後台(原名 MOMO Pro)| +| 產品類型 | B2B 電商監控與管理系統(商品價格監控、活動管理、業績分析)| +| 語言 | 繁體中文 (zh-TW) | +| 渲染方式 | Server-side Jinja2 (Flask),無 SPA | +| CSS 框架 | Bootstrap 5.3.3 | +| 圖標庫 | Font Awesome 6.0.0 | +| 圖表 | Chart.js 3.9.1 + ECharts 5.4.3 | + +--- + +## 2. 設計語言宣言 + +> **"Claude 暖系 × Nothing Phone 點陣機械感"** + +### 核心哲學 +- **底色**:溫暖米紙感(`#ebe6dc`)— 不刺眼的辦公室頁面底 +- **主調**:焦糖橘(`#c96442`)— 暖而有力的品牌色,取自 Claude AI 色調 +- **黑白對比**:Nothing Phone 風格的純黑側欄(`#1a1a1a`)搭配鮮明白字 +- **排版**:標題用 JetBrains Mono 等寬字,機械儀表板感;內文用 Inter + Noto Sans TC +- **裝飾**:8px 點陣背景(dot matrix)作為深色卡片的背景紋路 +- **陰影哲學**:線條優先(`1px solid rgba(...)`),避免大陰影,方角為主(radius 最大 6px) + +--- + +## 3. 兩套 Layout 系統(重要!現況) + +目前有**兩個共存的 Base Template**,正處於從舊版遷移至新版的過渡期: + +### 3-A. 舊版(`base.html` + `_navbar.html`) +- **Layout**:頂部固定 Navbar,全頁 container 排版 +- **Navbar**:深藍漸層 `#1e3c72 → #2a5298`,固定頂部 `position: fixed` +- **背景**:淺灰冷色 `linear-gradient(135deg, #f5f7fa 0%, #e8ecf1 100%)` +- **Accent 顏色**:藍紫漸層 `#667eea → #764ba2`(舊品牌色,仍在多數頁面中) +- **使用頁面**:`dashboard.html`、`sales_analysis.html`、`monthly_summary_analysis.html` 等主要功能頁 + +### 3-B. 新版(`ewoooc_base.html` + `ewoooc-tokens.css` + `ewoooc-shell.css`) +- **Layout**:CSS Grid Sidebar(240px)+ 主內容區,Topbar 64px sticky +- **Sidebar**:純黑背景 `#1a1a1a`,白色文字,焦糖橘 accent 高亮 +- **背景**:米色紙張感 `#ebe6dc` +- **Accent 顏色**:焦糖橘 `#c96442`(新品牌色) +- **使用頁面**:`vendor_stockout/`、部分新功能頁面(EwoooC 路由下的頁面) + +**設計方向**:以 3-B 新版為目標方向,所有優化/新設計請對齊新版設計系統。 + +--- + +## 4. 完整 Design Token(Source of Truth) + +### 4.1 色彩系統 + +#### 背景層次(米色系) +``` +--momo-bg-body: #ebe6dc ← 頁面底色(最深) +--momo-bg-surface: #faf7f0 ← 卡片表面(預設) +--momo-bg-elevated: #fdfaf3 ← 懸浮/選中卡片 +--momo-bg-subtle: #e2dccf ← 分隔區塊底色 +--momo-bg-muted: #cfc7b5 ← 更暗的區塊 +--momo-bg-paper: #f3eee2 ← Sidebar 底色、特殊卡片 +``` + +#### 文字層次(暖墨系) +``` +--momo-text-primary: #2a2520 ← 主要內文 +--momo-text-secondary: #645c52 ← 次要說明 +--momo-text-tertiary: #9b9081 ← 標籤標題、placeholder +--momo-text-disabled: #c4baa8 ← 禁用狀態 +--momo-text-inverse: #faf7f0 ← 深色背景上的白字 +--momo-text-link: #c96442 ← 連結色 +--momo-text-link-hover:#8f4530 ← 連結懸停 +``` + +#### 主色調(焦糖橘) +``` +--momo-accent: #c96442 ← 主 accent,Button/Active/Badge +--momo-accent-50: #fbf2ef ← 極淡(hover 背景) +--momo-accent-100: #f5e1d9 ← 淡(選中 tag 背景) +--momo-accent-200: #ecc3b3 ← 較淡 +--momo-accent-500: #c96442 ← = accent(主) +--momo-accent-600: #b1543a ← hover 按鈕 +--momo-accent-700: #8f4530 ← active/pressed 按鈕 +--momo-accent-soft: rgba(201,100,66,0.12) ← 懸停背景 +``` + +#### 狀態色(去飽和,適配米色底) +``` +成功 success: text #2a7a3f | bg #e3ebd9 | border #c5d4b0 +危險 danger: text #b5342f | bg #f0d8d4 | border #d9b1ac +警告 warning: text #b88416 | bg #f3e7c4 | border #d9c590 +資訊 info: text #2d5d80 | bg #d8e2ea | border #b5c5d2 +``` + +#### 邊框與分隔線 +``` +--momo-border: #2a2520 (實線邊框) +--momo-border-light: rgba(42,37,32,0.16) ← 淡分隔線 +--momo-border-focus: #c96442 ← 聚焦狀態 +--momo-divider: rgba(42,37,32,0.12) +``` + +#### 導航色(Nothing 黑)— Sidebar 專用 +``` +sidebar background: #1a1612(深暖黑) +nav-link hover bg: rgba(201,100,66,0.12)(accent-soft) +nav-link active bg: #c96442(實色焦糖橘) +nav-link active text: #faf7f0(反色) +status-card border: rgba(201,100,66,0.35)(橘色描邊) +``` + +### 4.2 Typography + +#### 字型堆疊 +``` +Display(標題): "JetBrains Mono", "Space Mono", "SF Mono", Menlo, Consolas, monospace +Body(內文): "Inter", -apple-system, "PingFang TC", "Noto Sans TC", "Microsoft JhengHei", sans-serif +Mono(數據): "JetBrains Mono", "SF Mono", Menlo, Consolas, monospace +``` + +#### 字型大小 +``` +xs → 0.75rem (12px) ← 極小標籤 +sm → 0.8125rem (13px) ← 次要內文、導航項目 +base → 0.9375rem (15px) ← 主要內文 +lg → 1.0625rem (17px) ← 稍大內文 +xl → 1.625rem (26px) ← 頁面主標題 +2xl → 2.25rem (36px) ← 大數字顯示 +``` + +#### 字重與行高 +``` +normal: 400 base line-height: 1.5 +medium: 500 tight line-height: 1.15(標題) +semibold: 600 loose line-height: 1.7(長文) +bold: 700 +black: 800(品牌名稱、數字展示用) +``` + +#### 特殊文字工具類 +```css +.momo-display → JetBrains Mono,標題用,搭配 font-feature-settings: "tnum", "ss01" +.momo-mono → 等寬字體,數字/代碼,搭配 font-feature-settings: "tnum" +.momo-label → JetBrains Mono 10px,font-weight 600,letter-spacing 0.12em,text-transform uppercase + 用於分類標題、狀態標籤(如 "監控" "營運" "系統") +``` + +### 4.3 間距系統(8px 基數) +``` +--momo-space-1: 0.25rem (4px) +--momo-space-2: 0.5rem (8px) +--momo-space-3: 0.75rem (12px) +--momo-space-4: 1rem (16px) +--momo-space-5: 1.5rem (24px) +--momo-space-6: 2rem (32px) +--momo-space-7: 3rem (48px) +--momo-space-8: 4rem (64px) +``` + +頁面內容區 padding:`28px 32px 40px`(desktop),`20px 16px 32px`(mobile) + +### 4.4 圓角系統(方角優先) +``` +--momo-radius-sm: 2px (0.125rem) ← badge、code、shortcut +--momo-radius-md: 4px (0.25rem) ← 按鈕、輸入框、卡片 → 預設 +--momo-radius-lg: 6px (0.375rem) ← 較大卡片、modal +--momo-radius-pill: 50rem ← 圓形按鈕、chip +--momo-radius-circle: 50% ← 頭像 +``` + +> 注意:舊版頁面(dashboard.html)使用更大的圓角(16px~20px)。新設計請用 4px~6px。 + +### 4.5 陰影系統(線條感優先) +``` +--momo-shadow-sm: 0 0 0 1px rgba(26,26,26,0.08) +--momo-shadow-md: 0 0 0 1px rgba(26,26,26,0.10) +--momo-shadow-lg: 0 12px 40px -8px rgba(26,26,26,0.18), 0 0 0 1px rgba(26,26,26,0.10) +--momo-shadow-colored: 0 0 0 2px rgba(201,100,66,0.25) ← accent 聚焦輪廓 +``` + +> 陰影哲學:主要靠 `1px solid border` 定義邊界,大投影只用於 Modal/Popover。 + +### 4.6 動畫系統 +``` +--momo-duration-fast: 0.12s ← hover/focus 狀態切換 +--momo-duration-normal: 0.2s ← sidebar 展收、dropdown +--momo-duration-slow: 0.4s ← Modal 出現、頁面過渡 +--momo-ease-in-out: cubic-bezier(0.4, 0, 0.2, 1) +--momo-ease-out: cubic-bezier(0, 0, 0.2, 1) + +基礎過渡(所有互動元件預設): + color / background-color / border-color / box-shadow,各 0.12s ease-in-out +``` + +特殊動畫: +```css +@keyframes momo-pulse-dot ← sidebar 底部爬蟲狀態 live dot(2s 閃爍) +@keyframes momo-fade-in ← 元素出現(opacity + translateY 2px) +@keyframes momo-slide-up ← Toast 進場(opacity + translateY 12px) +``` + +### 4.7 Z-Index 層級 +``` +1 → base +1000 → dropdown +1020 → sticky topbar +1030 → fixed elements +1040 → modal backdrop +1050 → modal +1060 → popover +1070 → tooltip +1080 → toast +``` + +### 4.8 Layout 尺寸 +``` +sidebar width: 240px +sidebar collapsed width: 72px(1180px 以下自動切換) +topbar height: 64px +``` + +--- + +## 5. 版面結構(新版 EwoooC Shell) + +``` +┌─────────────────────────────────────────────────────┐ +│ .momo-shell (CSS Grid: sidebar | main) │ +│ │ +│ ┌──────────┐ ┌──────────────────────────────────┐ │ +│ │ .momo- │ │ .momo-main-shell │ │ +│ │ sidebar │ │ │ │ +│ │ │ │ ┌──────────────────────────────┐│ │ +│ │ Logo │ │ │ .momo-topbar (sticky, 64px) ││ │ +│ │ Nav │ │ │ [hamburger] [search] [user] ││ │ +│ │ Groups │ │ └──────────────────────────────┘│ │ +│ │ │ │ │ │ +│ │ Status │ │ ┌──────────────────────────────┐│ │ +│ │ Card │ │ │ .momo-content (28px 32px pad) ││ │ +│ │ │ │ │ {% block ewooo_content %} ││ │ +│ │ │ │ └──────────────────────────────┘│ │ +│ └──────────┘ └──────────────────────────────────┘ │ +└─────────────────────────────────────────────────────┘ +``` + +### Sidebar 細節 +- **Logo 區**:`momo-logo-mark`(3×3 點陣格,黑底白點,右上角點空缺)+ `momo-brand-name`(JetBrains Mono, 18px, 800 weight) +- **Nav Group 標題**:`.momo-label`(10px 大寫等寬),右側延伸分隔線 `::after` +- **Nav Link**:左 icon(16px)+ label + 右側數字代碼(opacity 0.48) + - Default:透明底,暖墨字 + - Hover:`accent-soft` 背景(rgba(201,100,66,0.12)) + - Active:`accent` 實色背景(#c96442),白字 +- **Status Card**:sidebar 底部,暖黑底 `#1a1612` + 橘色描邊 + 點陣背景紋,顯示爬蟲狀態 + +### Topbar 細節 +- 左:mobile 漢堡選單(hidden on desktop) +- 中左:搜尋框(flex-grow,最大 480px),`⌘K` 快捷鍵 badge +- 中右彈性空間 +- 右側:排程時間 pill(深黑底+橘邊)、圖示按鈕(問號/鈴鐺)、使用者 chip(頭像+姓名+角色) + +--- + +## 6. 元件規格 + +### 6.1 按鈕 + +#### Primary Button(CTA) +``` +背景:--momo-accent (#c96442) +文字:--momo-text-inverse (#faf7f0) +邊框:none +圓角:--momo-radius-md (4px) +Hover:--momo-accent-600 (#b1543a) +Active:--momo-accent-700 (#8f4530) +Padding:9px 20px +Font:0.875rem,600 weight +``` + +#### Secondary / Ghost Button +``` +背景:transparent +文字:--momo-text-primary (#2a2520) +邊框:1px solid --momo-border-light +圓角:4px +Hover:bg --momo-bg-subtle +``` + +#### Danger Button +``` +背景:--momo-danger (#b5342f) +文字:white +``` + +#### Icon Button(.momo-icon-button) +``` +尺寸:36×36px +背景:transparent +圓角:4px +Hover:bg --momo-bg-subtle +Color:--momo-text-secondary → primary on hover +``` + +### 6.2 卡片(Card) +``` +背景:--momo-bg-surface (#faf7f0) +邊框:1px solid --momo-border-light +圓角:--momo-radius-lg (6px) +陰影:--momo-shadow-sm +Padding:24px (1.5rem) +Hover(可選):--momo-shadow-md +``` + +特殊:深色 Status Card(sidebar 底部) +``` +背景:--momo-ink-strong (#1a1612) +邊框:1px solid rgba(201,100,66,0.35) +點陣背景紋路:radial-gradient dots,6px×6px +文字:rgba(250,247,240,0.55) 標題 / rgba(250,247,240,0.62) 內文 +``` + +### 6.3 表格 +``` +Border:none(預設),行底部 border-bottom: 1px solid --momo-divider +Header:--momo-bg-paper 底色,--momo-label 樣式欄位標題 +Hover Row:--momo-accent-soft 背景 +Cell Padding:12px 16px +Data 數字:.momo-mono 字體 +狀態 Badge:pill 形(border-radius: 2px),對應狀態色 +``` + +### 6.4 徽章(Badge) +``` +圓角:2px(方角) +Font:10px,800 weight,全大寫 +Color:--momo-text-inverse (#faf7f0) +背景:--momo-accent(主要),或對應狀態色 +Padding:1px 6px +``` + +### 6.5 搜尋框(.momo-search-box) +``` +高度:38px +背景:--momo-bg-paper +邊框:1px solid --momo-border +圓角:4px +Placeholder:--momo-text-secondary +Focus border:--momo-accent +左側 icon:fa-search,margin-right 10px +右側:⌘K shortcut badge(accent 背景,2px 圓角) +``` + +### 6.6 Toast 通知 +``` +位置:右上角固定(top: 24px, right: 24px),z-index: 1080 +寬度:max 360px +圓角:6px +自動消失:3秒 +進場動畫:slide-up(opacity + translateY 12px → 0) +類型色:成功綠 / 危險紅 / 警告黃 / 資訊藍 +``` + +### 6.7 Modal +``` +背景:--momo-bg-surface +圓角:--momo-radius-lg (6px) +Header:--momo-ink 背景,白字(舊版用 #667eea 漸層,新版請改用暖墨色) +Backdrop:rgba(26,26,26,0.70) +最大寬度:sm 400px / md 640px / lg 960px / xl 1140px +``` + +### 6.8 用戶頭像 Chip(.momo-user-chip) +``` +高度:40px +Padding:4px 10px 4px 4px +圓角:pill(50rem) +Avatar:32×32px 圓形,背景 --momo-ink,白字,13px 800weight +Hover:bg --momo-bg-subtle +``` + +### 6.9 點陣裝飾(.momo-dot-bg) +```css +background-image: radial-gradient(circle, rgba(26,26,26,0.12) 1px, transparent 1px); +background-size: 8px 8px; +``` +用於深色背景卡片(Status Card)或特殊區塊裝飾。 + +--- + +## 7. 導航結構 + +``` +Sidebar Nav +├── 【監控】 +│ ├── 01 商品看板 / → fa-border-all +│ ├── 02 活動看板 /edm → fa-bullhorn +│ └── 03 分析報表 /sales_analysis → fa-chart-line +├── 【營運】 +│ ├── 04 廠商缺貨 /vendor-stockout → fa-box-open +│ ├── 05 AI 助手 /ai_recommend → fa-wand-magic-sparkles +│ └── 06 雲端匯入 /auto_import → fa-download +└── 【系統】 + └── 07 系統管理 /settings → fa-gear +``` + +舊版 Navbar(base.html 頁面)另有獨立下拉選單結構(業績分析/AI助手/系統管理各含多個子項目)。 + +--- + +## 8. 主要頁面清單 + +> **完整逐頁深度盤點請見 [`claude_design_brief_pages.md`](claude_design_brief_pages.md)(Appendix A)** +> 該附錄涵蓋 41 個 template + 3 個 component,每頁包含路徑/路由/Layout/區塊/元件/互動/配色/設計問題/改造重點 9 個欄位。 + +### 8.1 設計風格分布總覽(深掃後校正) + +實際發現 **4 種視覺風格混雜**,不只 2 種: + +| 風格 | 背景 | Accent | 頁面數 | 處置 | +|------|------|--------|--------|------| +| **A. 深藍 + 藍紫漸層**(舊主流)| `#f5f7fa` | `#1e3c72→#2a5298` + `#667eea→#764ba2` | 23+ | **全部遷移至 B** | +| **B. 焦糖橘暖系**(目標) | `#ebe6dc` | `#c96442` | 6+ | 維持並擴張 | +| **C. 紫色獨立入口頁** | `#667eea→#764ba2` | `#4F46E5` | 4 (login/403/maintenance/_loading) | 改用焦糖橘色相 | +| **D. GitHub Dark Terminal** | `#0d1117` | `#3fb950/#58a6ff/#bc8cff` | 2 (ai_automation_smoke/code_review) | 保留深色但與焦糖橘對齊 | + +### 8.2 風格 B(目標設計)參考頁面 + +Claude Design 可直接參考這幾頁的 Pattern 作為其他頁面遷移的樣板: + +| 頁面 | 路徑 | 為何是樣板 | +|------|------|-----------| +| `vendor_stockout_index_v2.html` | `/vendor-stockout` | 風格 B 最完整實作:Hero+Pulse Box+KPI Grid+Flow Cards+Summary | +| `edm_dashboard_v2.html` | `/edm/dashboard_v2` | 含 Chart.js 整合的範例(Modal 內動態載圖)| +| `vendor_stockout_import_v2.html` | `/vendor-stockout/import` | Dropzone + 多狀態面板(File/Progress/Result/Error)| +| `_ewoooc_shell.html` | (component) | Sidebar 導航結構與 Status Card | + +### 8.3 重點優化頁面 Top 10 + +依照「使用頻率 × 設計債務 × 影響範圍」排序: + +| 排名 | 頁面 | 行數 | 主要債務 | +|------|------|------|---------| +| 1 | `dashboard.html` | 1405 | 流量最大首頁,舊藍紫,無空狀態 | +| 2 | `sales_analysis.html` | 3165 | 系統最複雜頁,三種圖表庫並存 | +| 3 | `web/templates/vendor_stockout/list.html` | 1793 | 1600+ 行 inline JS 無模組化 | +| 4 | `daily_sales.html` | 1905 | 自訂月曆 878 行 CSS,行動版爆表 | +| 5 | `settings.html` | 1650 | 巨型設定頁,padding 重疊 bug | +| 6 | `monthly_summary_analysis.html` | 1473 | ECharts 待換 Chart.js | +| 7 | `edm_dashboard_v2.html` | 1130 | 已是 B 風格但 CSS 600+ 行待精簡 | +| 8 | `ai_recommend.html` | 1000+ | AI 互動 UX 流式輸出未實作 | +| 9 | `user_management.html` | 906 | 權限矩陣未視覺化 | +| 10 | `logs.html` | 872 | 篩選器邏輯複雜,最佳重構樣本 | + +--- + +## 9. 色彩衝突說明(設計遷移中) + +目前有**三種 Accent 色系混用**,這是技術債,新設計一律使用 **焦糖橘**: + +| 色系 | Hex | 出現位置 | 備注 | +|------|-----|---------|------| +| 焦糖橘 ✓ | `#c96442` | ewoooc 新版頁面 | **目標設計色** | +| 藍紫 ✗ | `#667eea → #764ba2` | dashboard.html、表格 header、按鈕 | 舊版,待替換 | +| 深藍 ✗ | `#1e3c72 → #2a5298` | 舊版 Navbar、base.html | 舊版,待替換 | + +--- + +## 10. 響應式斷點 + +| 斷點 | 寬度 | 行為 | +|------|------|------| +| Desktop | ≥1180px | Sidebar 240px 全展開,顯示文字 | +| Collapsed | 1180px–820px | Sidebar 收至 72px,只顯示圖標 | +| Mobile | ≤820px | Sidebar 隱藏(slide-in 抽屜式),hamburger 按鈕出現 | + +Topbar 漸進隱藏(Container Query): +- ≤1024px:隱藏排程 pill +- ≤880px:隱藏使用者名稱/角色 +- ≤720px:隱藏搜尋框文字 + +--- + +## 11. 圖表設計規範 + +### Chart.js(折線圖、柱狀圖、圓餅圖) +目前使用舊藍紫色系,應遷移至: +``` +主線色:#c96442(焦糖橘) +輔助線:#b5342f(暗紅,下跌)、#2a7a3f(深綠,上漲) +Fill 漸層:從 rgba(201,100,66,0.3) → rgba(201,100,66,0.05) +格線:rgba(42,37,32,0.06)(極淡) +Tooltip:背景 rgba(26,26,26,0.88),白字,焦糖橘描邊 +``` + +### ECharts(複雜多維圖表) +應使用同色系調色板,確保視覺一致。 + +--- + +## 12. 互動模式 + +| 模式 | 實現方式 | +|------|---------| +| 表格行點擊展開詳情 | onclick → Bootstrap Modal | +| KPI Card 點擊鑽取 | onclick → Modal + fetch API | +| 搜尋篩選 | form GET 提交(非 SPA) | +| 操作回饋 | Toast 通知(右上角滑入) | +| 複製品號 | Clipboard API + 視覺回饋(文字變 ✅ 已複製)| +| 載入狀態 | Bootstrap spinner-border | +| 全頁載入 | 自訂 overlay 動畫(WOOO 品牌)| + +--- + +## 13. 品牌資產 + +### Logo 變體 +``` +logo.png → 標準 logo +logo_transparent.png → 透明底 +logo_v4_gradient.png → 漸層版 +logo_v4_glass.png → 玻璃質感 +logo_navbar.svg → 導航列向量版 +logo_circle.svg → 圓形版 +``` + +### 品牌名稱 +- 老品牌:**WOOO** / **WOOO TECH** +- 新品牌:**EwoooC**(等寬字體呈現,副標:「價格監控 V2」) + +### Logo Mark(點陣格設計) +``` +3×3 格,32×32px,gap 1.5px,padding 5px +背景:--momo-ink (#1a1612),圓角 2px +點:圓形白點 +中心格(第5格):空白 +→ 點陣 ⠿ 風格,Nothing Phone 美學 +``` + +--- + +## 14. 當前設計亮點(可保留延伸) + +1. **JetBrains Mono 標題** — 機械儀表板感,數字排版優秀 +2. **點陣背景裝飾** — Status Card 深色背景上的橘色點陣,獨特品牌感 +3. **Live Dot 動畫** — 橘色脈衝點,搭配發光陰影 `box-shadow: 0 0 8px #c96442` +4. **Nav Code 數字** — 每個導航項目右側的灰色數字代碼(01~07),Like a terminal +5. **焦糖橘 × 暖墨 × 米白** — 三色搭配溫暖而不俗氣 +6. **⌘K 搜尋框** — 開發者友善,鍵盤優先設計 + +--- + +## 15. 當前設計弱點(優化方向) + +1. **兩套設計系統未統一** — 舊版藍紫 vs 新版焦糖橘,視覺割裂感強 +2. **Dashboard 主頁** — 流量最大的頁面仍用舊版,設計待升級 +3. **大圓角** — 舊版 16~20px border-radius,與新版方角設計衝突 +4. **無 Dark Mode** — Design Token 已備妥,只缺 JS 實現 +5. **Modal Header** — 舊版用藍紫漸層,應統一為暖墨色 +6. **缺共用元件庫** — 卡片/表格/表單 Pattern 散落各頁面,未抽象化 +7. **無 Focus Style** — Accessibility 待加強(focus-visible 輪廓) +8. **空狀態 (Empty State)** — 多數頁面僅有文字,缺圖示/插圖 + +--- + +## 16. 技術限制(設計時須知) + +- **無 CSS Build Tool**:無法使用 SCSS/PostCSS,所有 CSS 必須是原生 CSS 或內嵌 ` + diff --git a/templates/components/_ewoooc_shell.html b/templates/components/_ewoooc_shell.html index f62d016..ccd0ee3 100644 --- a/templates/components/_ewoooc_shell.html +++ b/templates/components/_ewoooc_shell.html @@ -8,6 +8,7 @@ #} {% set _active_page = active_page|default('') %} +{% set _analysis_pages = ['sales', 'daily_sales', 'monthly', 'growth'] %} {% set _obs_pages = [ 'obs_overview', 'obs_agent_orchestration', 'obs_business_intel', 'obs_host_health', 'obs_ai_calls', 'obs_budget', @@ -18,40 +19,64 @@ +