
Google 從 Chrome 138 版本開始將 Gemini Nano 模型直接嵌入桌面客戶端。這是一種輕量級的大語言模型,在使用者裝置本地執行,所有資料處理都在端側完成,無需上傳至伺服器。瀏覽器會自動管理模型的分發、更新,並充分利用裝置的 GPU、NPU 或 CPU 進行硬體加速。
支持 AI 功能的 Chrome 需要 138 版本以上。
語言偵測器和翻譯器 API 適用於 Chrome 電腦版。這些 API 無法在行動裝置上運作。在 Chrome 中,只要符合下列條件,即可使用 Prompt API、Summarizer API、Writer API、Rewriter API 和 Proofreader API:
設裝置型號優化為 Enabled
chrome://flags/#optimization-guide-on-device-model
設 Gemini Nano 提示 API 為 Enabled
chrome://flags/#prompt-api-for-gemini-nano
複製到地址欄打開。
Language Detector: Disable
Translator: Disable
Summarizer: Disable
<textarea id="language-detector-text">Guten Tag, mein Name ist Bexon Bai. Ich bin Mobile-Entwickle und spezialisiere mich auf Android- und iOS-Technologie.</textarea>
<textarea id="language-detector-result" disabled style="margin-top: 8px;display:none"></textarea>
<textarea id="translator-text">Guten Tag, mein Name ist Bexon Bai. Ich bin Mobile-Entwickle und spezialisiere mich auf Android- und iOS-Technologie.</textarea>
截止至 Chrome 138 版本,僅支持 [en, es, ja]
<textarea id="summarizer-text" style="margin-top: 8px;"></textarea>
create-dmg 是一个用于构建精美 dmg 镜像的 shell 脚本。目前大多数开发者,包括 Apple 官方,都在使用 dmg 镜像来分发 Mac App,本文将使用 create-dmg 来制作一个 app 分发镜像。
安装 create-dmg:
通过 Homebrew
brew install create-dmg
通过 Github 官方仓库
创建时你需要准备好你的 app 和一张引导拖拽的背景图(文中提供了一张我准备好的),文件目录:
NewEmptyFolder
- MacApp.app
- backgroung.svg
<button id="download-svg-btn" style="padding: 10px 20px; background-color: #0066cc; color: white; border: none; border-radius: 5px; cursor: pointer; font-size: 14px;">下載 SVG</button>
進入資料夾所在的目錄開啟 Terminal。然後執行以下命令:
create-dmg \
--volname "Application Installer" \
--background "background.svg" \
--window-pos 400 200 \
--window-size 660 400 \
--icon-size 100 \
--icon "Application.app" 160 160 \
--hide-extension "Application.app" \
--app-drop-link 500 160 \
"Application.dmg" \
"Application.app/"
雙擊開啟 Application.dmg

如果你有準備icns圖示,也可以設定映象的圖示:
--volicon "AppIcon.icns" \

在 app 的 build.gradle 的 android 標籤下加入如下
signingConfigs {
debug {
v1SigningEnabled true
v2SigningEnabled true
}
release {
v1SigningEnabled true
v2SigningEnabled true
}
}
即使想在 Steam 上玩的遊戲有 Windows 版但沒有 Mac 版的情況並不少見。因此,我試著利用 Sikarugir 在 Mac 上執行 Windows 版的遊戲。
※ Mac 版 Steam 已安裝且 Steam 帳號已建立。
先安裝:
自己的情況下,我透過下述方法成功啟動了 Luma Island
在「應用程式資料夾」中雙擊「Sikarugir Creator.app」,
點選「+」下載「WS12WineCX24.0.7_6」引擎
點選「Create New Blank Wrapper」
安裝成功後在 User 的 Applications 檔案夾找到 app 開啟
/Users/{user_name}/Applications/Sikarugir/SteamWindows.app
點選 Winetricks ,勾選
點選 Run,等待安裝完成
點選 Windows app 後面的 Broswe 找到 Steam.exe
在路徑後面新增
-udpforce -allosarches -cef-force-32bit
最後大概是這個樣子
"C:\Program Files (x86)\Steam\Steam.exe" -udpforce -allosarches -cef-force-32bit
點選 「Test Run」啟動。
啟動測試了一下 Luma Island
.
├── Feature/
│ ├── Data/
│ │ ├── Network/
│ │ │ ├── CloudUserApi.swift
│ │ │ ├── CloudUserRequest.swift
│ │ │ └── CloudUserResponse.swift
│ │ └── Service/
│ │ └── CloudApiService.swift
│ ├── Domain/
│ │ ├── Entities/
│ │ │ └── UserEntity.swift
│ │ └── UseCases/
│ │ ├── CloudUserUseCase.swift
│ │ └── CloudSyncUseCase.swift
│ ├── Presentation/
│ │ └── CloudUserViewModel.swift
│ └── UI/
│ ├── Components/
│ │ ├── UserRowView.swift
│ │ ├── UserRowView.swift
│ │ └── UserRowView.swift
│ └── UserListView.swift
└── UserApp.swift
@main
struct UserApp: App {
var body: some Scene {
WindowGroup {
UserListView()
}
}
}
#Preview {
UserListView()
}
</details>
import SwiftUI
struct UserListView: View {
@StateObject private var viewModel = CloudUserViewModel()
var body: some View {
NavigationView {
ZStack {
if viewModel.isLoading {
ProgressView("Loading users...")
} else if let error = viewModel.errorMessage {
VStack(spacing: 16) {
Image(systemName: "exclamationmark.triangle")
.font(.system(size: 48))
.foregroundColor(.orange)
Text("Error")
.font(.headline)
Text(error)
.font(.subheadline)
.foregroundColor(.secondary)
.multilineTextAlignment(.center)
.padding(.horizontal)
Button("Retry") {
Task {
await viewModel.loadUsers()
}
}
.buttonStyle(.borderedProminent)
}
} else {
List(viewModel.filteredUsers) { user in
NavigationLink(destination: UserDetailView(user: user)) {
UserRowView(user: user)
}
}
.searchable(text: $viewModel.searchText, prompt: "Search users")
}
}
.navigationTitle("Users")
.toolbar {
ToolbarItem(placement: .navigationBarTrailing) {
Button {
Task {
await viewModel.syncUsers()
}
} label: {
Image(systemName: "arrow.clockwise")
}
.disabled(viewModel.isLoading)
}
}
}
.task {
await viewModel.loadUsers()
}
}
}
</details>
<details>
<summary>Feature/UI/Components/UserRowView.swift</summary>
import SwiftUI
struct UserRowView: View {
let user: UserEntity
var body: some View {
VStack(alignment: .leading, spacing: 8) {
Text(user.name)
.font(.headline)
HStack {
Image(systemName: "person.circle")
.foregroundColor(.blue)
Text(user.username)
.font(.subheadline)
.foregroundColor(.secondary)
}
HStack {
Image(systemName: "building.2")
.foregroundColor(.purple)
Text(user.company)
.font(.subheadline)
.foregroundColor(.secondary)
}
}
.padding(.vertical, 4)
}
}
</details>
<details>
<summary>Feature/UI/Components/UserDetailView.swift</summary>
import SwiftUI
struct UserDetailView: View {
let user: UserEntity
var body: some View {
List {
Section("Personal Information") {
DetailRow(icon: "person.fill", title: "Name", value: user.name)
DetailRow(icon: "at", title: "Username", value: user.username)
DetailRow(icon: "envelope.fill", title: "Email", value: user.email)
DetailRow(icon: "phone.fill", title: "Phone", value: user.phone)
DetailRow(icon: "globe", title: "Website", value: user.website)
}
Section("Location") {
DetailRow(icon: "location.fill", title: "City", value: user.city)
}
Section("Company") {
DetailRow(icon: "building.2.fill", title: "Company", value: user.company)
VStack(alignment: .leading, spacing: 4) {
HStack {
Image(systemName: "quote.opening")
.foregroundColor(.blue)
Text("Catch Phrase")
.font(.subheadline)
.foregroundColor(.secondary)
}
Text(user.catchPhrase)
.font(.body)
.italic()
.padding(.leading, 24)
}
}
}
.navigationTitle(user.name)
.navigationBarTitleDisplayMode(.inline)
}
}
</details>
<details>
<summary>Feature/UI/Components/DetailRow.swift</summary>
import SwiftUI
struct DetailRow: View {
let icon: String
let title: String
let value: String
var body: some View {
HStack {
Image(systemName: icon)
.foregroundColor(.blue)
.frame(width: 24)
VStack(alignment: .leading, spacing: 2) {
Text(title)
.font(.caption)
.foregroundColor(.secondary)
Text(value)
.font(.body)
}
}
}
}
</details>
import Combine
import Foundation
class CloudUserViewModel: ObservableObject {
@Published var users: [UserEntity] = []
@Published var isLoading = false
@Published var errorMessage: String?
@Published var searchText = ""
private let getUsersUseCase = CloudUserUseCaseImpl()
private let syncUseCase = CloudSyncUseCaseImpl()
var filteredUsers: [UserEntity] {
if searchText.isEmpty {
return users
}
return users.filter { user in
user.name.localizedCaseInsensitiveContains(searchText) ||
user.username.localizedCaseInsensitiveContains(searchText) ||
user.email.localizedCaseInsensitiveContains(searchText) ||
user.company.localizedCaseInsensitiveContains(searchText)
}
}
func loadUsers() async {
isLoading = true
errorMessage = nil
do {
users = try await getUsersUseCase.execute()
} catch {
errorMessage = error.localizedDescription
}
isLoading = false
}
func syncUsers() async {
isLoading = true
errorMessage = nil
do {
try await syncUseCase.execute()
users = try await getUsersUseCase.execute()
} catch {
errorMessage = error.localizedDescription
}
isLoading = false
}
}
</details>
struct UserEntity: Identifiable {
let id: Int
let name: String
let username: String
let email: String
let city: String
let company: String
let phone: String
let website: String
let catchPhrase: String
}
</details>
<details>
<summary>Feature/Domain/UseCases/CloudUserUseCase.swift</summary>
protocol CloudUserUseCase {
func execute() async throws -> [UserEntity]
}
class CloudUserUseCaseImpl: CloudUserUseCase {
private let service: CloudApiService
init(service: CloudApiService = CloudApiServiceImpl()) {
self.service = service
}
func execute() async throws -> [UserEntity] {
let responses = try await service.getUsers()
return responses.map { response in
UserEntity(
id: response.id,
name: response.name,
username: response.username,
email: response.email,
city: response.address.city,
company: response.company.name,
phone: response.phone,
website: response.website,
catchPhrase: response.company.catchPhrase
)
}
}
}
</details>
<details>
<summary>Feature/Domain/UseCases/CloudSyncUseCase.swift</summary>
protocol CloudSyncUseCase {
func execute() async throws
}
class CloudSyncUseCaseImpl: CloudSyncUseCase {
private let service: CloudApiService
init(service: CloudApiService = CloudApiServiceImpl()) {
self.service = service
}
func execute() async throws {
try await service.syncUsers()
}
}
</details>
struct CloudUserRequest: Codable {
var endpoint: String = "/users"
}
</details>
<details>
<summary>Feature/Data/Network/CloudUserResponse.swift</summary>
struct CloudUserResponse: Codable {
let id: Int
let name: String
let username: String
let email: String
let address: Address
let phone: String
let website: String
let company: Company
struct Address: Codable {
let street: String
let suite: String
let city: String
let zipcode: String
let geo: Geo
struct Geo: Codable {
let lat: String
let lng: String
}
}
struct Company: Codable {
let name: String
let catchPhrase: String
let bs: String
}
}
</details>
<details>
<summary>Feature/Data/Network/CloudUserApi.swift</summary>
import Foundation
protocol CloudUserApi {
func fetchUsers() async throws -> [CloudUserResponse]
}
class CloudUserApiImpl: CloudUserApi {
private let baseURL = "https://jsonplaceholder.typicode.com"
func fetchUsers() async throws -> [CloudUserResponse] {
guard let url = URL(string: "\(baseURL)/users") else {
throw URLError(.badURL)
}
let (data, _) = try await URLSession.shared.data(from: url)
let users = try JSONDecoder().decode([CloudUserResponse].self, from: data)
return users
}
}
</details>
<details>
<summary>Feature/Data/Service/CloudApiService.swift</summary>
protocol CloudApiService {
func getUsers() async throws -> [CloudUserResponse]
func syncUsers() async throws
}
class CloudApiServiceImpl: CloudApiService {
private let api: CloudUserApi
init(api: CloudUserApi = CloudUserApiImpl()) {
self.api = api
}
func getUsers() async throws -> [CloudUserResponse] {
return try await api.fetchUsers()
}
func syncUsers() async throws {
_ = try await api.fetchUsers()
}
}
</details>
因為中國大陸網路複雜度的原因,GitHub 在不同時間,不同寬頻運營商,不同省份的訪問速度和可到情況都不同。
而 GitHub 承擔了絕大部分的 swift package 的分發。
但 Xcode 的網路請求並不會透過系統 proxy,這時看著 fetching... 就會幹著急。
Xcode 雖然不經過系統的 proxy,但是 Terminal 可以呀!
使用終端進入專案 root 目錄
複製並在終端執行終端代理命令(這裡以 clash 為演示)

export https_proxy=http://127.0.0.1:7897 http_proxy=http://127.0.0.1:7897 all_proxy=socks5://127.0.0.1:7897
再執行 xcodebuild 的 fetch swift package 的命令就好啦~
xcodebuild -resolvePackageDependencies -scmProvider system
當你看到 resolved source packages 就大功告成!
祝程式設計愉悅!
因為 git 本身允許修改 commit,本文章指導如何在 macOS 作業系統中使用 GPG 對 git 提交進行數字簽名。透過這種方式,提交在 GitHub 或其它 git 程式碼管理系統上將被標記為已驗證,從而增加提交的可信度。🎉
透過 Homebrew 安裝 GPG:
brew install gpg
安裝 GPG 工具,之後可以使用它來生成和管理金鑰對。
檢查現有的 GPG 金鑰
在生成新的 GPG 金鑰對之前,首先檢查是否已經存在 GPG 金鑰。執行以下命令:
gpg --list-secret-keys --keyid-format LONG
如果命令沒有返回任何金鑰,說明你還沒有配置 GPG 金鑰。
% gpg --list-secret-keys --keyid-format LONG
gpg: directory '/Users/UserName/.gnupg' created
gpg: /Users/UserName/.gnupg/trustdb.gpg: trustdb created
如果返回了金鑰資訊,則可以跳過生成 GPG 金鑰對的步驟。
% gpg --list-secret-keys --keyid-format LONG
[keyboxd]
---------
sec ed25519/E7175B0CAF8C4FE0 2024-09-16 [SC]
*****************
uid [ultimate] UserName <[email protected]>
ssb cv25519/88CAD4AB29BCB4B5 2024-09-16 [E]
後面也可以透過這條命令檢視已有的金鑰
生成新金鑰對:
gpg --full-generate-key --expert
# 使用 --expert 選項可以支援 ECC
# 加密演算法。如果選擇 RSA 演算法,則無需使用 --expert 選項
獲取金鑰列表:
gpg --list-secret-keys --keyid-format LONG
示例輸出:
% gpg --list-secret-keys --keyid-format LONG
gpg: checking the trustdb
gpg: marginals needed: 3 completes needed: 1 trust model: pgp
gpg: depth: 0 valid: 1 signed: 0 trust: 0-, 0q, 0n, 0m, 0f, 1u
[keyboxd]
---------
sec ed25519/E7175B0CAF8C4FE0 2024-09-16 [SC]
*****************
uid [ultimate] UserName <[email protected]>
ssb cv25519/88CAD4AB29BCB4B5 2024-09-16 [E]
記下 sec ed25519/ 後面的 GPG Key ID,例如 E7175B0CAF8C4FE0
匯出公鑰:
gpg --armor --export E7175B0CAF8C4FE0
將匯出的公鑰複製並貼上到 GitHub 的 GPG 金鑰新增頁面上。
-----BEGIN PGP PUBLIC KEY BLOCK-----
PUBLIC KEY
-----END PGP PUBLIC KEY BLOCK-----
設定 gpg-agent 環境變數:
echo 'export GPG_TTY=$(tty)' >> ~/.zshrc
source ~/.zshrc
這裡只寫了Oh my zsh的,其它工具自行改一下
配置 Git 使用 GPG 簽名:
當前目錄
git config user.signingkey E7175B0CAF8C4FE0
git config commit.gpgsign true
全域性
git config --global user.signingkey E7175B0CAF8C4FE0
git config --global commit.gpgsign true
注意替換為實際的 GPG Key ID。
對於不需要 GPG 簽名的 Git 倉庫,進入倉庫目錄並執行:
git config commit.gpgsign false
祝程式設計愉悅!
ssh-keygen -t ed25519 -C "[email protected]"
執行後會出現提示:
Enter file in which to save the key: 直接回車使用預設名稱 id_ed25519Enter passphrase: 建議設置密碼以增加安全性(也可以直接回車跳過)Enter same passphrase again: 再次輸入密碼確認提示: 如果你使用的是舊系統不支援
ed25519,可以使用ssh-keygen -t rsa -b 4096 -C "[email protected]"替代。
SSH 對檔案權限要求嚴格,否則金鑰可能被忽略:
chmod 700 ~/.ssh
chmod 600 ~/.ssh/id_ed25519
chmod 644 ~/.ssh/id_ed25519.pub
# 啟動 ssh-agent
eval "$(ssh-agent -s)"
# 編輯或創建 config 檔案,實現自動載入
vi ~/.ssh/config
按 i 進入編輯模式,加入以下內容:
Host *
AddKeysToAgent yes
UseKeychain yes
IdentityFile ~/.ssh/id_ed25519
按 esc 然後輸入 :wq 儲存退出。
# 新增金鑰到 ssh-agent
ssh-add --apple-use-keychain ~/.ssh/id_ed25519
# 啟動 ssh-agent
eval "$(ssh-agent -s)"
# 新增金鑰
ssh-add ~/.ssh/id_ed25519
# 啟動 ssh-agent
eval "$(ssh-agent -s)"
# 新增金鑰
ssh-add ~/.ssh/id_ed25519
pbcopy < ~/.ssh/id_ed25519.pub
# 使用 xclip(需先安裝: sudo apt install xclip)
xclip -selection clipboard < ~/.ssh/id_ed25519.pub
# 或直接顯示並手動複製
cat ~/.ssh/id_ed25519.pub
clip < ~/.ssh/id_ed25519.pub
如果你使用多個 Git 託管服務或多個帳號,可以編輯 ~/.ssh/config:
vi ~/.ssh/config
按 i 進入編輯模式,加入配置:
# GitHub 個人帳號
Host github.com
HostName github.com
User git
PreferredAuthentications publickey
IdentityFile ~/.ssh/id_ed25519
# GitHub 工作帳號
Host github-work
HostName github.com
User git
PreferredAuthentications publickey
IdentityFile ~/.ssh/id_ed25519_work
# Bitbucket
Host bitbucket.org
HostName bitbucket.org
User git
PreferredAuthentications publickey
IdentityFile ~/.ssh/bitbucket_id_ed25519
# GitLab
Host gitlab.com
HostName gitlab.com
User git
PreferredAuthentications publickey
IdentityFile ~/.ssh/gitlab_id_ed25519
按 esc 然後輸入 :wq 儲存。
使用工作帳號時:
git clone git@github-work:company/repo.git
ssh -T [email protected]
成功的回應:
Hi <username>! You've successfully authenticated, but GitHub does not provide shell access.
ssh -T [email protected]
ssh -T [email protected]
可能原因:
解決方法:
# 檢查權限
ls -la ~/.ssh
# 確認金鑰已載入
ssh-add -l
# 如果沒有,重新新增
ssh-add ~/.ssh/id_ed25519
# 詳細除錯模式
ssh -vT [email protected]
The authenticity of host 'github.com' can't be established.
ED25519 key fingerprint is SHA256:xxx...
Are you sure you want to continue connecting (yes/no/[fingerprint])?
輸入 yes 並按回車(注意要完整輸入 yes,不能只按回車)。
參考步驟 3 的 config 配置,確保已加入 AddKeysToAgent yes。
chmod 700 ~/.ssh
chmod 600 ~/.ssh/id_ed25519
chmod 644 ~/.ssh/id_ed25519.pub
chmod 600 ~/.ssh/config
id_ed25519)如果你的專案目前使用 HTTPS,可以切換到 SSH:
# 查看當前遠端 URL
git remote -v
# 更改為 SSH URL
git remote set-url origin [email protected]:username/repo.git
# 確認更改
git remote -v
祝程式設計愉悅! 🚀
如果遇到問題,可以使用 ssh -vvv [email protected] 查看詳細除錯資訊。
Lizard 是一個沒什麼用的 Planet 小瀏覽器