標籤: 銷售工具

  • 腦子系統小白指南:10 步驟從零做到完整 AI 工作流

    重點摘要(TL;DR)

    • 前 9 篇是給做過的人看的設計 / 實作 / 修補。本篇是給「沒做過、想做到那樣」的人 — 10 步驟從零到 v2.3 完整工作流。
    • 10 步驟:裝 CC → 寫 CLAUDE.md → 開始 brain → spawn 1 agent → Agent Team 並行 → 行動端 → 加分級 → Gateway → Ollama → 完整 v2.3。每一步都能單獨用,合起來變成完整體系。
    • 不需要一次做完。每一步停下來都能用,不會卡死。Step 1-3 是 1 週體感巨變,Step 4-6 是 Agent Team 開花,Step 7-10 是資安升級。
    • 不是寫程式 tutorial,是工作流改造指南。每一步都跟你怎麼做事的方式有關 — brain 改你「怎麼累積知識」、Agent Team 改你「怎麼處理複雜任務」、Gateway 改你「怎麼處理敏感資料」。
    • 本文是腦子系統第 10 篇 / 入門篇。前 9 篇連結在文末。

    誰該讀這篇

    • 有寫 code / 用 terminal 經驗,但沒系統性用過 AI 工作流
    • 看過前面 9 篇覺得有道理,但不知道從哪開始
    • 想做出完整 AI 工作流(腦子 + Agent Team + 跨平台 + 資安),不只是聊天用 ChatGPT
    • 願意花 1-3 個月漸進改造,不追求一週搞定

    不該讀這篇:完全沒寫過 code、沒用過 terminal — 那需要先補基礎(git / shell / markdown)。

    終點長什麼樣(預覽)

    10 步驟全部做完後,你的日常工作流會變成:

    你 (Claude Code) — 設了 ANTHROPIC_BASE_URL → Gateway
       ├─ 普通 prompt → Gateway 看分級 → cloud / 地端 自動路由
       ├─ Spawn Agent Team(7 個 opus 並行)→ 每個 agent 走 Gateway,獨立分級
       ├─ 寫到敏感字 → 自動切地端,cloud 流量歸零
       └─ 行動端透過 ccbot / Telegram → 同樣經 Gateway
    
    旁邊 brain 系統(~/.claude/projects/.../memory/)
       ├─ 全域規則 CLAUDE.md(自動載)
       ├─ 領域 brain markdown(LLM 看了知道踩過什麼坑)
       └─ 每個 brain 有 sensitivity_level: A/B/C
           └─ Gateway 自動同步字典做路由
    

    實際感受:

    • 寫 code 比以前快 3-5 倍(LLM 看過你 brain 不會犯重複錯)
    • 複雜任務不用親自跑,7 個 agent 平行做,你 review 結果
    • 不再擔心客戶資料貼進 ChatGPT(地端自動接管)
    • 離開電腦也能繼續(手機 LINE / Telegram → ccbot → 你的工作流)

    10 步驟總覽

    Step 做什麼 時間 階段體感
    1裝 Claude Code(或 Cursor / Continue)30 分鐘能跟 LLM 對話寫 code
    2寫第一份 CLAUDE.md(規則層)30 分鐘LLM 開始遵守你的習慣
    3建立 brain markdown(知識層)1 週累積不再講同樣的話兩次
    4Spawn 1 個 agent(Agent Team 入門)1 小時學會把工作 delegate
    5多 agent 並行(Agent Team 進階)1 小時同時跑 7 個任務
    6行動端通訊(ccbot / 官方 channel)1 小時手機也能繼續工作流
    7加 sensitivity_level 分級(資安 1)30 分鐘brain 開始分敏感度
    8裝 Gateway(資安 2)30 分鐘prompt 自動分流
    9加 Ollama 地端(資安 3)1 小時A 級資料永不上雲
    10完整 v2.3(B 級脫敏 + tests)半天production-ready

    關鍵:不要連續做完。Step 1-3 做完跑 1-2 週,習慣後再做 4-6,習慣後再做 7-10。跳級會崩潰

    Step 1:裝 Claude Code

    Claude Code(以下簡稱 CC)是 Anthropic 官方的 terminal AI coding 工具。也可選 Cursor、Continue、OpenCode 等替代品 — 概念一樣,本文以 CC 示範。

    # macOS / Linux,需要 Node 18+
    npm install -g @anthropic-ai/claude-code
    
    # 登入(用 Anthropic 帳號 OAuth 或 API key)
    claude
    
    # 在某個專案資料夾跑
    cd ~/your-project
    claude

    其他工具的安裝請查官方 docs:Claude Code Quickstart / Cursor / Continue / OpenCode

    第一個 prompt 試試:

    $ claude
    > 看一下這個專案的 README,告訴我是做什麼的

    能讀檔、回應 — 你已經在 step 1。跑幾天感受 LLM 怎麼讀你的 codebase

    Step 2:寫第一份 CLAUDE.md(規則層)

    CC 會自動讀你 home 目錄下的 ~/.claude/CLAUDE.md(全域規則)+ 專案根目錄的 ./CLAUDE.md(專案特定)。這就是「腦子」第一層。

    # 寫第一份(全域)
    mkdir -p ~/.claude
    cat > ~/.claude/CLAUDE.md << 'EOF'
    # 全域規則
    
    ## 我的習慣
    - 一律用繁體中文回應
    - code 不寫超過必要的註解
    - commit message 用 feat: / fix: / docs: prefix
    
    ## 我的環境
    - macOS / Linux mini PC
    - Python 3.12 / Node 20
    
    ## 不要做的事
    - 不要主動 git commit(等我說才 commit)
    - 不要安裝 dev dependency 沒問過
    EOF

    馬上感受差別:重啟 CC,再問同樣問題,LLM 已經會用中文回 + 不會自作主張 commit。這就是規則層的價值 — 你不用每次重複講

    原則:條目少而精,3-10 條最好。寫 30 條沒人記得住(包括 LLM)。

    Step 3:開始寫 brain markdown(知識層)

    規則是「你想要什麼」。Brain 是「你踩過什麼坑」。

    mkdir -p ~/.claude/projects/your-project/memory/brain

    第一份 brain 範例(假設你寫過 Kafka 踩過坑):

    cat > ~/.claude/projects/your-project/memory/brain/kafka.md << 'EOF'
    ---
    name: kafka
    type: technical
    ---
    # Kafka 我踩過的坑
    
    ## consumer rebalance 一直跑
    - 症狀:consumer group 每隔幾分鐘 rebalance,訊息處理停頓 30 秒
    - 原因:max.poll.interval.ms 預設 5 分鐘,業務邏輯處理超過會觸發
    - 解法:max.poll.interval.ms 拉到 15 分鐘 + 業務邏輯拆 batch
    
    ## 訊息順序錯亂
    - 同一個 partition 才保證順序
    - 多 partition 一定要設 partition key(預設 hash key)
    EOF

    更新 CLAUDE.md 引用 brain:

    echo "
    ## Domain Brain
    - [Kafka](projects/your-project/memory/brain/kafka.md)
    " >> ~/.claude/CLAUDE.md

    累積策略(關鍵):每次踩坑後 5 分鐘寫進對應 brain。不要等月底整理一次 — 那永遠不會發生。

    1 週後感受:LLM 開始知道「Kafka 你不會犯哪些錯」「OSGi 你踩過哪些雷」。同樣 prompt 一個月前要解釋 5 分鐘,現在 LLM 直接 hit brain 給對的答案

    Step 4:Spawn 一個 agent(Agent Team 入門)

    到這步你已經會用 LLM 寫 code + 累積 brain。下一個跨越:讓 LLM 派出小弟做事

    CC 內建 Agent tool。在 CC 裡:

    > 派一個 agent 看 ~/myproject/src/ 底下所有 .py 檔,
      找出沒寫 type hint 的函式,列清單給我

    CC 會 spawn 一個 sub-agent,sub-agent 自己跑 grep / read,跑完回報。你不用看那 100 個檔

    啟發點:任何「我想做但要花 1-2 小時看資料的事」都可以 delegate。你變成 manager,不是 doer

    Step 5:多 agent 並行(Agent Team 進階)

    真正威力:並行 spawn 多個 agent

    > 同時派 7 個 agent:
       1. agent A: review 我新寫的 OAuth 模組安全
       2. agent B: 看 .github/workflows 有沒有 CI 改進空間
       3. agent C: 找 README 跟實際 code 不一致的地方
       4. agent D: 算這個 codebase 的 test coverage
       5. agent E: 看 dependencies 有沒有過期
       6. agent F: 列所有 TODO 註解
       7. agent G: 找硬編碼的密碼 / token
    
    7 個並行,2 分鐘後給我一份 dashboard

    CC 會用 Agent tool 並行 spawn 7 個,各自獨立 context、各自查資料、回報。這是傳統工作流不可能做到的

    記憶體規則:LLM 推理在 cloud,本機跑的是 CC sub-agent process 本身。粗估每個 opus agent ~1 GB / sonnet ~600 MB / haiku ~400 MB,7 個 opus 並行 ~7 GB,先 free -h 確認 available 夠 +2 GB buffer。16 GB 機器跑得動但要關掉其他大耗 RAM 程式,32 GB 比較舒服。
    真正吃 RAM 的是本地 LLM:Step 9 的 Ollama 跑 14b 模型要 ~10 GB,跟 sub-agent process 加起來才是負載 — 16 GB 機器若同時跑 7 個 opus agent + Ollama 14b 會 swap 重災,建議改 7b 級模型或升級到 32 GB+。

    Step 6:行動端通訊(ccbot / 官方 channel)

    到這步你已經是 desktop power user。下一步:離開電腦也能繼續工作流

    兩個選項:

    • 官方 channel(2026/3 Anthropic 推出):MCP server 接 Telegram / Discord / iMessage,設定簡單。官方文件
    • ccbot(six-ddc/ccbot):Telegram 接 tmux,decouple from SDK,1 個 Telegram topic = 1 個 tmux window = 1 個 CC session

    ccbot 安裝:依官方 README(因為安裝方式可能更新)— https://github.com/six-ddc/ccbot。流程大致是:

    1. 去 Telegram @BotFather 申請 bot token + 開 Threaded Mode
    2. 依 README 用 uv tool installpipx install 裝 ccbot
    3. TELEGRAM_BOT_TOKEN + ALLOWED_USERS 環境變數
    4. 裝 hook 讓 CC tmux session 自動連 Telegram

    官方 channel 安裝(2026/3 Anthropic 推出):依 Claude Code Channels 官方文件,設定更簡單,但只支援 Anthropic 官方 endpoint。

    感受:通勤路上想到 bug,Telegram 一句話 → ccbot → 桌機 CC 開始跑 → 你下車回家結果已在。
    (ccbot 限 Telegram;若用 LINE,需自己寫 LINE bot bridge,或改用官方 channel 接 iMessage / Discord)

    Step 7:加 sensitivity_level 分級(資安第 1 道)

    到這步你 brain 累積了不少。但有些 brain 含敏感資訊(客戶名、家裡網路、內部專案代號)。一旦 LLM 走 cloud,這些就送出去了。

    第一道防線:brain frontmatter 標 sensitivity_level

    # brain/kafka.md(技術知識,公開可用)
    ---
    name: kafka
    type: technical
    sensitivity_level: C   # 純技術,可上 cloud
    ---
    
    # brain/client_alpha_oncall.md(客戶資料)
    ---
    name: client_alpha_oncall
    type: business_incident
    sensitivity_level: A   # A 級,絕對不上 cloud
    ---

    分級原則:

    • A 級:洩漏會出事(客戶名 / 家裡 IP / 個資 / 合約 / 配方)
    • B 級:能脫敏後送 cloud(內部 process 名 / 員工名)
    • C 級:純技術 / 開源 / 公開知識

    這步看起來只是改 frontmatter,但 讓你開始用「分級」眼光看資訊,為下一步 Gateway 鋪路。

    (Step 8 後回來做)從 brain 自動同步到 Gateway 字典

    等 Step 8 把 Gateway clone 下來後,回頭做這個同步,讓 brain 跟 Gateway 用一份字典:

    # 從所有 A 級 brain 抽 placeholder(例 [client_xxx] / [project_xxx])
    grep -h "sensitivity_level: A" -A 100 ~/.claude/projects/*/memory/brain/*.md \
      | grep -oP '\[client_\w+\]|\[project_\w+\]|\[employee_\w+\]' \
      | sort -u > ~/walsin-gateway/A_keywords.txt
    
    # 改 gateway_v2_cc.py 的 A_KEYWORDS list 從檔案 load:
    #   A_KEYWORDS = open(os.path.expanduser("~/walsin-gateway/A_keywords.txt")).read().splitlines()
    # 取代原本 hardcoded 的 ["[client_alpha]", ...]

    核心想法:一個 sensitivity_level 欄位,brain 跟 Gateway 兩邊都用 — 不用手動維護兩套字典。

    Step 8:裝 Gateway(資安第 2 道)

    分級標好了,但 LLM 不會自動知道。需要 Gateway 在「prompt 命中 A 級字典」時把 LLM 流量切到地端。

    用我寫的 v2.3 版(674 行 FastAPI):

    # clone Gist
    gh gist clone c82c51ae2a73bfe640dec5b61e5a542a walsin-gateway
    cd walsin-gateway
    
    # 裝套件
    pip install --user fastapi uvicorn httpx tiktoken
    
    # (若已做 Step 7 字典同步)Gateway 自動讀 ~/walsin-gateway/A_keywords.txt
    # (還沒做)先用 gateway_v2_cc.py 預設的字典,跑通後再回頭做 Step 7 同步
    
    # 啟動(必設 MASTER_KEY,用 export 不能用 inline env)
    export MASTER_KEY=sk-$(openssl rand -hex 16)
    echo "記下這把 key,別 commit、別寫進 tracked .env: $MASTER_KEY"
    
    # 啟動 Gateway 背景跑
    python3 gateway_v2_cc.py &
    
    # CC 切過去
    export ANTHROPIC_BASE_URL=http://localhost:4000
    export ANTHROPIC_AUTH_TOKEN=$MASTER_KEY

    從此你 prompt 命中字典 → 自動切地端。但這時還沒裝 Ollama,只是 Gateway 就位。完整體驗看 Step 9。

    ⚠️ 安全提醒(必看):Gateway 預設 bind 0.0.0.0(所有網卡),若你跑在筆電或公共 wifi,別人掃到 port 4000 就能試你的 master key,把 Gateway 當公網 proxy 借走你的 Anthropic API 額度。本機開發必須鎖回 127.0.0.1:編 gateway_v2_cc.py 末段的 uvicorn.run(...),把 host="0.0.0.0" 改成 host="127.0.0.1"。公網部署需 reverse proxy + TLS + 第二層 auth,不在本指南範圍。

    Step 9:加 Ollama 地端(資安第 3 道)

    # 裝 Ollama
    curl -fsSL https://ollama.com/install.sh | sh
    
    # 拉模型(看你硬體)
    ollama pull qwen3:14b      # 14B,中等強度,16GB RAM 跑得動
    ollama pull qwen3:1.7b     # 輕量,當 fail-safe

    到這步,完整路由生效:

    • 你寫「客戶 X 的訂單問題」→ Gateway 命中 A 級 → Ollama 14b 處理 → 不出本機
    • 你寫「Kafka rebalance 怎麼解」→ Gateway 沒命中 → cloud Claude → 全速 Opus 4.7

    實際感受:95% 工作跟原本一樣爽,只有 5% 命中字典的會慢一點 — 但那些任務本來就不該上雲。

    Step 10:完整 v2.3(B 級脫敏 + tests)

    v2.3 額外有:

    • B 級脫敏 fallback:中等敏感資料,地端壞了能脫敏後送 cloud(B 級走 cloud 時 response 帶 X-Gateway-Sanitized: 1 header)
    • Auth 防 substring 攻擊:secrets.compare_digest 精確比對
    • SSE byte-stream 直通:streaming 不變形
    • 24 個 pytest:跑 pytest test_gateway.py -v 全綠才上線
    • benchmark_runner.py:多模型對比 runner
    • demo_record.sh:asciinema 60 秒 demo 自動化

    跑 pytest 的前提:

    pip install --user pytest pytest-asyncio
    cd ~/walsin-gateway
    # sk-test-secret 是 test fixture 預設值;真實使用換成 openssl rand -hex 16 產生的 key
    MASTER_KEY=sk-test-secret python3 -m pytest test_gateway.py -v
    # 應看到 24 passed

    到這步你的工作流是 production-ready 的。能拿給公司 IT 看,有立場提內部 PoC

    不同階段你會得到什麼(別跳級)

    完成 Step 你的 superpower 建議停留
    1-3LLM 認得你的習慣 + 不再重複講同樣的話2 週
    4-5manager 模式 — delegate 而不是 do2 週
    6脫離桌機,工作流跟著你走1 週
    7-9敏感資料 + AI 生產力可同時擁有2 週
    10production-ready,可推給公司穩定使用

    最常見的失敗模式:跳級。沒寫過 brain 就裝 Gateway → 字典空的,Gateway 沒用;沒玩過 Agent Team 就跑 7 個 agent → 機器 OOM 崩潰。每階段穩了再下一階段

    跟前 9 篇對應

    本篇 Step 對應九部曲深入閱讀
    1-3 規則 + brain第 1 篇 (Why) + 第 2 篇 (How)
    4-5 Agent Team第 4 篇 (Tools) Harness 段
    6 行動端第 4 篇 (Tools) 的 ccbot / 官方 channel 段
    7 分級第 7 篇 (ISO) A/B/C 分級
    8-9 Gateway + 地端第 9 篇 (Proof) 完整 v2.3
    10 完整 production所有篇章 + Gist 完整 code

    踩坑警告(過來人提醒)

    • 不要先看 9 篇藍圖再開始 — 會被嚇到動彈不得。先做 Step 1-3,有感再看藍圖
    • 不要追求完美 brain — 寫得醜但有資訊比寫得漂亮但沒人看好
    • 不要 spawn 太多 agent — 機器 RAM 16GB 跑 7 個 opus 會 OOM,先 free -h 確認
    • 不要把 Iron Rules 寫 30 條 — 沒人記得住,3-10 條最好
    • 不要 Step 8 Gateway 上線就斷網 — 沒設 ANTHROPIC_API_KEY 時 fallback 地端,但本來工作流可能有依賴 cloud 的習慣,慢慢適應
    • 不要假裝 Agent Team 取代 review — agent 出的東西還是要看,他們是 fast doer 不是 quality gate

    結語:不要追求一週搞定

    10 步驟看似可以一週做完,但每一步的「習慣養成」需要時間

    Step 3 累積 brain 你會經歷「寫了 5 個又懶了」「再撿起來」「逐漸變成反射」。沒這 3 週適應期,Step 4 派 agent 你會不知道讓他做什麼。

    Step 5 並行 agent 你會經歷「派 7 個但 review 不過來」,然後學會「派 3 個但每個任務切清楚」。這也是要時間。

    這篇文章是地圖,不是腳本。照走 1 個月,你會擁有跟前 9 篇文章作者一樣的工作流。再走 3 個月,你會發展出自己的版本,可能比這個更好。

    這就是「我可以怎麼做到現在這樣」的答案。10 步驟,1-3 個月,從零到 v2.3

    延伸閱讀:腦子系統 10 篇

  • 給銷售的 AI 工具:LLM 產自助 HTML × ERP CRUD × 即時資訊圖

    重點摘要(TL;DR)

    • 銷售/業務人員在 Telegram 一句話「幫我畫上週每個客戶訂單金額長條圖」 → LLM 30 秒產出 self-contained HTML 檔案 → 員工瀏覽器點開 → 看到即時圖表。
    • HTML 是 standalone 純靜態,從 CDN 載 Chart.js + Tailwind,內含 JS 透過公司 Gateway 對接 iDempiere REST(不直連 ERP)。
    • 四層架構:Generation(LLM 產)/ Execution(瀏覽器跑)/ Data(Gateway proxy)/ Rendering(Chart.js 渲染)
    • 混合模式:LLM 產 HTML 時 inline 一份預載資料(打開立即顯示),HTML 內按「重新整理」可主動 fetch 最新值。速度和即時性兼得
    • 安全設計:HTML 不含 secrets、Gateway 認 SSO、LLM 產的 HTML 用 textContent 防 XSS、Gateway 校驗 OData filter 防 injection、員工只看到 AD_Role 允許的資料。
    • 本文是腦子系統的第六篇,前五篇:Why / How / Scale / Tools / ERP

    一、使用情境

    銷售 Tom 在通勤路上滑手機,腦子裡想到「**等等開會要秀上週業績**」。

    傳統流程(沒有這個工具)

    1. 到公司打開電腦
    2. 登入 ERP
    3. 找到訂單視窗
    4. 篩選日期
    5. 匯出 Excel
    6. 用 PowerPoint / Excel 畫圖
    7. 合計花 30-60 分鐘

    新流程(本文設計)

    1. Telegram 對 bot 說:「上週每個客戶訂單金額長條圖」
    2. 30 秒後 bot 回 HTML 檔(或連結)
    3. 點開,圖表立刻渲染
    4. 開會時直接全螢幕秀
    5. 合計花 30 秒

    關鍵:銷售不需要學任何工具、不需要安裝 app、不需要 IT 部署任何東西。產出的 HTML 還可以分享給同事、存證、離線重看。

    二、四層架構

    銷售 Tom (Telegram chat)
       ↓
    [Generation 層] LLM 看公司腦 + 員工 prompt
       ↓ 產 self-contained HTML
    HTML 檔案 (Chart.js + Tailwind 從 CDN 載)
       ↓ Tom 在瀏覽器打開
    [Execution 層] 瀏覽器跑 HTML 內 JS
       ↓ JS 對 https://gateway.example.com/erp/query 發 fetch
    [Data 層] 公司 Gateway (LiteLLM + Portkey + 自製 ERP proxy)
       ↓ Gateway 帶 Tom 的 SSO 身份
       ↓ 呼叫 iDempiere MCP server / REST API
       ↓ iDempiere AD_Role 自動過濾資料
       ↓ 回 JSON
    HTML 內 JS 接到 JSON
       ↓
    [Rendering 層] Chart.js 渲染圖表 / 動態表格 / 資訊圖

    四層各自的職責清晰、可獨立替換:

    • Generation:換 LLM(Claude/GPT/本地 Qwen)不影響其他層
    • Execution:瀏覽器標準環境,任何裝置都能跑(Mac / Windows / iPhone Safari)
    • Data:Gateway 換成內部 service mesh、ERP 換成另一套都不影響 HTML
    • Rendering:換 Chart.js 為 ECharts / Plotly 只改前端,後端不動

    三、混合模式:預載 + 即時刷新

    LLM 產生 HTML 時面對一個取捨:

    模式 優點 缺點
    A 純 inline:LLM 把資料寫死進 HTML 簡單、離線可看、無 CORS 資料是快照,要新查就要重新產
    B 純 fetch:HTML 啟動才查 每次最新 打開時白屏 1-2 秒、需連線
    C 混合(推薦):預載 + 重新整理按鈕 立即顯示 + 隨時更新 + 離線可看快照 HTML 較大(包含初始資料)

    實務上 C 混合模式最佳。實作:LLM 在產 HTML 時順便呼叫一次 Gateway 拿初始資料,把 JSON 寫進 HTML 的 const initialData = [...],同時保留 refresh() 函數讓員工按按鈕主動更新。

    四、LLM 怎麼產 HTML — Prompt 設計

    給 LLM 的 system prompt 要包含五件事:

    1. HTML 模板骨架:固定的 head / body 結構,用哪個 CDN 圖表庫
    2. Gateway URL 與 API schema:fetch 要打哪、payload 格式
    3. 可用 ERP table 與欄位:從公司腦讀(C_Order / C_BPartner / M_Product 等的可查欄位)
    4. OData filter 語法:eq/neq/gt/contains 等(注意 iDempiere 用 neq 不是 ne)
    5. 安全規範:用 textContent 不用 innerHTML、不要 hardcode token、不要 eval()

    4.1 System Prompt 範例(精簡版)

    You are a HTML dashboard generator for sales staff.
    
    CONTEXT (from company brain):
    - Available ERP tables: C_Order, C_BPartner, M_Product, M_InOut
    - Common columns for C_Order: GrandTotal, DateOrdered, C_BPartner_ID, IsSOTrx
    - Filter syntax: OData (use 'neq' not 'ne')
    - Gateway endpoint: https://gateway.example.com/erp/query
    - Gateway auth: SSO cookies (credentials: 'include')
    
    OUTPUT REQUIREMENTS:
    1. Generate ONE complete self-contained HTML file
    2. Use Chart.js via CDN (https://cdn.jsdelivr.net/npm/chart.js)
    3. Use Tailwind via CDN (https://cdn.tailwindcss.com)
    4. Include initial data inline (call Gateway once and embed JSON)
    5. Provide a refresh() function for live update
    6. Use textContent (NEVER innerHTML) when displaying data
    7. Add a loading spinner during fetch
    8. Style: clean, presentation-ready (用得上開會秀客戶)
    
    USER QUERY: {{user_message}}

    五、產出 HTML 範例(完整可執行)

    下面是 LLM 看到「上週每個客戶訂單金額長條圖」這個 query 後產出的範例 HTML。這是真實可執行的 self-contained 檔案:

    <!DOCTYPE html>
    <html lang="zh-Hant">
    <head>
    <meta charset="utf-8">
    <title>上週訂單金額(by 客戶)</title>
    <script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
    <script src="https://cdn.tailwindcss.com"></script>
    </head>
    <body class="bg-slate-50 p-6 font-sans">
    
    <div class="max-w-4xl mx-auto">
      <div class="flex items-center justify-between mb-4">
        <h1 class="text-2xl font-bold">上週訂單金額(by 客戶)</h1>
        <button id="refreshBtn"
                class="bg-blue-600 text-white px-4 py-2 rounded hover:bg-blue-700">
          🔄 重新整理
        </button>
      </div>
      <p id="meta" class="text-sm text-slate-500 mb-4"></p>
      <canvas id="chart" height="120"></canvas>
      <table class="mt-6 w-full text-sm">
        <thead class="bg-slate-200">
          <tr><th class="text-left p-2">客戶</th><th class="text-right p-2">訂單數</th><th class="text-right p-2">金額</th></tr>
        </thead>
        <tbody id="tableBody"></tbody>
      </table>
    </div>
    
    <script>
    // === 預載資料(LLM 產生時 inline 進來) ===
    const initialData = [
      {customer: "客戶 A", orderCount: 5, amount: 1280000},
      {customer: "客戶 B", orderCount: 3, amount: 850000},
      {customer: "客戶 C", orderCount: 7, amount: 2100000},
      {customer: "客戶 D", orderCount: 2, amount: 420000}
    ];
    const generatedAt = "2026-05-02 09:30";
    
    // === Gateway 設定 ===
    const GATEWAY_URL = "https://gateway.example.com/erp/query";
    const QUERY = {
      table: "C_Order",
      filter: "DateOrdered ge '2026-04-25' and DateOrdered le '2026-05-01'",
      groupBy: "C_BPartner_ID",
      aggregate: ["count", "sum(GrandTotal)"]
    };
    
    // === 渲染函數 ===
    let chart;
    function render(data, ts) {
      // 注意:用 textContent 不用 innerHTML 防 XSS
      document.getElementById('meta').textContent = `資料時間:${ts}`;
    
      const tbody = document.getElementById('tableBody');
      tbody.textContent = '';
      data.forEach(row => {
        const tr = document.createElement('tr');
        tr.className = 'border-b';
        [row.customer, row.orderCount, row.amount.toLocaleString()].forEach((v, i) => {
          const td = document.createElement('td');
          td.className = i === 0 ? 'p-2' : 'p-2 text-right';
          td.textContent = v;
          tr.appendChild(td);
        });
        tbody.appendChild(tr);
      });
    
      if (chart) chart.destroy();
      chart = new Chart(document.getElementById('chart'), {
        type: 'bar',
        data: {
          labels: data.map(d => d.customer),
          datasets: [{
            label: '訂單金額(NTD)',
            data: data.map(d => d.amount),
            backgroundColor: 'rgba(59, 130, 246, 0.6)'
          }]
        },
        options: {
          responsive: true,
          plugins: {legend: {display: false}}
        }
      });
    }
    
    // === 即時刷新 ===
    async function refresh() {
      const btn = document.getElementById('refreshBtn');
      btn.disabled = true;
      btn.textContent = '⏳ 載入中...';
      try {
        const r = await fetch(GATEWAY_URL, {
          method: 'POST',
          credentials: 'include',  // 帶 SSO cookies
          headers: {'Content-Type': 'application/json'},
          body: JSON.stringify(QUERY)
        });
        if (!r.ok) throw new Error(`Gateway error ${r.status}`);
        const data = await r.json();
        render(data.rows, new Date().toLocaleString('zh-TW'));
      } catch (e) {
        alert('刷新失敗:' + e.message);
      } finally {
        btn.disabled = false;
        btn.textContent = '🔄 重新整理';
      }
    }
    
    // === 初始化 ===
    document.getElementById('refreshBtn').addEventListener('click', refresh);
    render(initialData, generatedAt);
    </script>
    
    </body>
    </html>

    這個檔案存成 orders.html,雙擊即可在瀏覽器打開。打開時看到預載資料(已渲染圖表 + 表格);按「重新整理」就 fetch 最新資料。整個檔案約 80 行,包含全部 logic

    六、安全設計(必看)

    6.1 HTML 端

    • 絕不在 HTML 寫 token / API key:HTML 是員工拿到的檔案,寫 token 等於洩漏。所有認證在 Gateway server side
    • ✅ 用 fetch(..., {credentials: 'include'}) 帶員工 SSO cookies
    • ✅ 渲染用 textContent,不用 innerHTML(防 LLM 產的 XSS)
    • 不用 eval()、Function() 等動態 code 執行
    • ✅ Chart.js / Tailwind 從固定 CDN 載(版本鎖定),不從不可信來源載

    6.2 Gateway 端

    • SSO 認證:員工已登入公司,cookies 自動帶,Gateway 認 user identity
    • OData filter 校驗:LLM 產生的 filter 要過 Gateway 校驗(白名單欄位、operator 限制),防 SQL injection / 越權查詢
    • Rate limit:單一員工每分鐘最多 X 個 query,防 LLM 產的迴圈失控
    • Audit log:每個 query 記錄(誰、何時、查什麼、回傳幾筆),進 SIEM
    • CORS 白名單:Gateway 只允許指定 origin(若 HTML 託管在內網檔案分享伺服器,設定該 origin)

    6.3 ERP 端

    • iDempiere AD_Role 自動套:Gateway 帶員工 token 進 iDempiere,業務 Tom 看不到 CFO 才看的到的資料
    • 不直連 ERP:HTML 的 fetch 不直接打 iDempiere,一律走 Gateway proxy。理由:ERP 不該暴露在 internet,Gateway 才是受控邊界
    • Process call 限制:銷售工具預設 read-only,要寫資料(下單、修改)需要更高層審核或專用工具

    七、CORS / 認證的具體做法

    三條路徑分析:

    路徑 CORS 認證 推薦
    HTML → 直連 iDempiere REST 需開 iDempiere CORS 設定 JWT token 存 HTML(危險) ❌ 不要
    HTML → 公司 Gateway → iDempiere Gateway 設 CORS 白名單 SSO cookies 自動帶 ✅ 推薦
    HTML → MCP server → iDempiere MCP server 設 CORS MCP OAuth 2.1 ⚠️ 進階(複雜但可)