Repository navigation
Conversation
901fb2b to
02e5fb2
Compare
|
Force-push:rebase 到目前的 main( 修正: 另外兩個 commit 內容沒變。push 前 |
02e5fb2 to
fc0e362
Compare
|
Force-push:AI 不再佔導覽列的第六格。Material 3 建議導覽列放 3 到 5 個 destination,6 個在小螢幕和部分語系上標籤會擠。
push 前 |
Platform: all New(zh-Hant): 首頁右上角新增 AI 助理:用你自己的 AI 服務問地震、天氣、颱風等,AI 會先查 DPIP 的即時資料再回答;金鑰只存在這支手機,對話不會儲存 New(en-US): a new AI assistant, opened from the top right of Home, answers questions about earthquakes, weather and typhoons with your own AI service, looking up DPIP's live data first; your key stays on this phone and conversations are not saved
1066fdf to
9c7c49d
Compare
|
Force-push:rebase 到目前的 main( 沒有衝突,三個 commit 的內容都沒變( |
這個 PR 做了什麼
在首頁右上角(地區列的右端)新增「AI 助理」入口,點開是蓋在 shell 上的全螢幕頁面;導覽列維持 5 格。使用者填入自己的 AI 服務(OpenAI 相容格式)的網址、金鑰和模型後,就能問地震、天氣、颱風、海嘯和災害通知。AI 會先透過 DPIP 的 MCP 伺服器(
/mcp)查即時資料,再用使用者的語言回答。第一次進入會先顯示說明頁,寫清楚資料會送到哪裡、哪些會留在手機上。說明頁也提供 MCP 伺服器網址,讓使用者接到自己的工具(Claude、ChatGPT 等)。
這個 PR 沒有 public AI:ExpTech 自己提供的 AI、限流與排隊,以及「不要濫用、歡迎贊助」的說明,之後另外處理。
改動內容
正式程式碼
d3da5961把 secret 存在平台的金鑰庫(core/platform/secret_store.dart)SecretStorePlugin.swift):Keychain generic password,kSecAttrAccessibleAfterFirstUnlockThisDeviceOnly,所以備份還原到別支手機時不會帶走。SecretStoreChannel.kt):AES-GCM 金鑰在 Android Keystore 內產生、不會離開 Keystore,只把密文寫到noBackupFilesDir。寫入先寫暫存檔再 rename;無法解密的檔案(例如移除螢幕鎖後 Keystore 金鑰失效)會刪掉,當成沒存過。SecretKeyenum 當 key,和SettingKey同樣的理由:不能用任意字串存取。SettingsStore:dpip.db在 application-support,iOS 會把它放進備份,這樣說明頁「金鑰不離開手機」就不成立。13b15577AI 助理(lib/features/ai_chat/)data/openai_chat_model.dart):OpenAI 相容的/chat/completions,stream: true。index)、reasoning_content、開頭的<think>…</think>(自架模型常見)、不串流的 JSON 回應、200 串流裡夾帶的錯誤(OpenRouter)。["null","integer"]改成integer,拿掉additionalProperties。DPIP 的 schema 用了這兩種寫法,Gemini 的相容端點會因此拒絕整個請求。data/dpip_mcp_tool_host.dart):initialize→tools/list(每個 session 列一次)→tools/call,回應 SSE 或 JSON 都能解析。ApiClient的coreApi,和其他 DPIP 請求一樣區域綁定、可以切換區域。instructions(單位、震度刻度、代碼格式)會放進系統提示。domain/chat_agent.dart):domain/system_prompt.dart):AppTime.utc8),模型才知道「今天」「一小時前」是什麼時候。https://,localhost例外。core/network/api_client.dart新增streamAbsolute:絕對網址、串流回應、回傳所有狀態碼。AI 服務的兩個呼叫都走它:串流回應不進 ETag 快取,而這些回應取決於使用者的金鑰(getAbsolute的 GET 會被快取)。SettingKeys.aiBaseUrl、aiModel、aiIntroSeen;金鑰不是 setting。shared/widgets/region_bar.dart新增可選的trailing:一個圖示按鈕放在地區列右端,跟著地區列一起淡出、滑走,用和地區 badge 相同的天空配色;有trailing時兩側留相同的 inset,選取中的地區仍在螢幕正中間。事件頁不傳,所以不變。context.pushNamed(AppRoutes.ai);首頁不 import AI 這個 feature。AppRoutes.ai和aiSettings都是蓋在 shell 上的頁面,不是 branch,導覽列 5 格、既有分頁的索引都沒變。離開再回來,對話還在(存在AiChatController,不在頁面裡)。9c7c49dd文件:api.md補上/mcp和使用者 AI 服務的兩個呼叫;ARCHITECTURE.md § Persistence說明 secret 為什麼是 SQLite 以外的唯一例外。測試
<think>、非串流回應、200 裡的錯誤、取消時中止請求。trailing在右端、按得到、跟著地區列消失;選取中的 badge 仍置中(只留右側 inset 時這個測試會失敗)。AppRoutes.ai。路由:shell 只有 5 個 branch,AI 是 shell 外的頁面。ApiClient(earthquake_api_test.dart、replay_session_test.dart)補上streamAbsolute。SecretStorePluginTests5 個,包含「Keychain 項目是ThisDeviceOnly」。沒做的與已知限制
http://(例如另一台電腦上的 Ollama)不能用,Android 的 network security config 只允許 localhost 走明文。相關 issue
沒有對應的 issue。
怎麼驗
tool/check.sh全部通過,Dart 測試 3,703 個全過(pre-push hook 也跑過一次)。DpipMcpToolHost(臨時測試,沒有 commit):instructions有 1,016 字元。township_lookup、earthquake_reports都查得到資料。validating /properties/limit: … want "integer",模型可以讀到並重試。/mcp在core-tnn1和core-tyo1都有回應。tool/dev/test_ios.sh用CODE_SIGNING_ALLOWED=NO)沒有 keychain entitlement,會拿到errSecMissingEntitlement(-34018),所以這種情況下會跳過。另外用 ad-hoc 簽章(tool/dev/test_ios.sh CODE_SIGNING_ALLOWED=YES CODE_SIGN_IDENTITY=-)跑過,5 個全部通過。檢查清單
tool/check/commits.sh origin/main..HEAD通過(3 個 commit)tool/commit.sh --push)AppLocalizations,12 個語系都有翻譯AppSpacing/AppRadius;深色模式與大字級還沒在實機上看過