根据提供的code differences信息,由于没有具体的代码变更内容,我将生成一个通用的commit message模板:

```
docs(readme): 更新文档说明

- 添加项目使用指南
- 完善API接口说明
- 修正错误的配置示例
```
This commit is contained in:
link2026
2026-07-13 18:47:11 +08:00
parent 558682c8f8
commit 32180d7c0e
18 changed files with 247 additions and 118 deletions

View File

@@ -39,7 +39,7 @@ actor AIRuntime {
private(set) var lastGenerateStats: GenerateStats?
/// ( / PPT )
/// MLX/GPU(Gemma-3n E2B);MNN/SME2 LLM
/// MLX/GPU(Gemma 4 E2B);MNN/SME2 LLM
var activeBackendLabel: String {
#if targetEnvironment(simulator)
return "MLX · CPU(模拟器)"
@@ -53,7 +53,7 @@ actor AIRuntime {
// MARK: - Gemini (hybrid: Gemma ,)
// , OOM , /
// / Gemma-3n(MLX )·
// / Gemma 4(MLX )·
private let gemini = GeminiBackend()
/// ( + key)UI
nonisolated var cloudAvailable: Bool { CloudAI.isConfigured }
@@ -132,7 +132,7 @@ actor AIRuntime {
#endif
}
/// ( MLX/GPU,Gemma-3n E2B),
/// ( MLX/GPU,Gemma 4 E2B),
func prepare() async throws {
// ,
// return: ready, generate

View File

@@ -63,7 +63,7 @@ enum GeminiError: Error, LocalizedError {
/// Google Gemini REST(URLSession) Generative Language API:
/// - `generate``streamGenerateContent`(SSE ), /
/// - `analyze``generateContent`(),/ Gemini
/// Gemma-3n(MLX )·(§)
/// Gemma 4(MLX )·(§)
///
/// ,** AIRuntime OOM **,
actor GeminiBackend {

View File

@@ -2,9 +2,14 @@ import Foundation
import MLX
import MLXLLM
import MLXLMCommon
// mlx-swift-lm 3.x HF Hub/ opt-in (MLXHuggingFace),
// `#hubDownloader()` / `#huggingFaceTokenizerLoader()` HuggingFace / Tokenizers
import MLXHuggingFace
import HuggingFace
import Tokenizers
/// MLX ,actor 线访
/// mlx-swift-examples 2.29.1(commit 9bff95ca) API
/// mlx-swift-lm 3.31.4 API(MLXLLM / MLXLMCommon)
actor LLMSession {
let container: ModelContainer
@@ -31,15 +36,20 @@ actor LLMSession {
}
/// ( config.json + weights + tokenizer)
/// Gemma-3n `<end_of_turn>`(token 106), eos `<eos>`(1);
/// `<end_of_turn>` maxTokens
/// Gemma 4 : `<turn|>`(token 106)(3n `<end_of_turn>`),
/// eos `<eos>`(1); `<turn|>` maxTokens
/// mlx-swift-lm `gemma4_e2b_it_4bit` extraEOSTokens
static func load(folderURL: URL) async throws -> LLMSession {
let configuration = ModelConfiguration(
directory: folderURL,
extraEOSTokens: ["<end_of_turn>"]
extraEOSTokens: ["<turn|>"]
)
// 3.31.4:loadContainer Downloader + TokenizerLoader(.directory)
// (resolve ),Downloader ; HF AutoTokenizer
let container = try await withDeviceOverride {
try await LLMModelFactory.shared.loadContainer(
from: #hubDownloader(),
using: #huggingFaceTokenizerLoader(),
configuration: configuration
)
}

View File

@@ -20,32 +20,41 @@ nonisolated enum ModelManifest {
/// :MNN `MNN`( HuggingFace taobao-mnn);MLX 沿 mlx-community
static func modelScopeRepo(for kind: ModelKind) -> String? {
switch kind {
case .llm: return "mlx-community/gemma-3n-E2B-it-lm-4bit" // ,
case .mnnLLM: return "MNN/Qwen3.5-2B-MNN" // ,
case .llm: return "mlx-community/gemma-4-e2b-it-4bit" // ,
case .mnnLLM: return "MNN/Qwen3.5-2B-MNN" // ,
case .vl: return nil // , / ,
}
}
/// hf-mirror(hf-mirror.com)退 id mlx-community HF org/name ;
/// MNN HuggingFace (taobao-mnn),,退revision HF `main`
static func huggingFaceMirrorRepo(for kind: ModelKind) -> String? {
switch kind {
case .llm: return kind.huggingFaceRepo // mlx-community/gemma-4-e2b-it-4bit
case .vl, .mnnLLM: return nil
}
}
static func files(for kind: ModelKind) -> [ModelFile] {
switch kind {
case .llm:
// Gemma-3n-E2B-it-lm-4bit:Gemma-3n (text-only), LLMModelFactory
// gemma3n (mlx-swift-lm "gemma3n")MLX ,
// ModelScope mlx-community/gemma-3n-E2B-it-lm-4bit blob
//(repo/files API,2026-06 ) README.md / .gitattributes / configuration.json
//( ModelScope ,MLX )model.safetensors ,
// MLX loadWeights glob *.safetensors,index.json ,
// tokenizer.model(SentencePiece) chat_template.jinja ,/
// gemma-4-e2b-it-4bit:Gemma 4 E2B(instruct,4bit)config.json model_type = "gemma4",
// LLMModelFactory "gemma4" (mlx-swift-lm 3.31.4 ;Gemma4Model
// language_model ,/)e2b ( gemma4_unified 12B)
// ModelScope mlx-community/gemma-4-e2b-it-4bit blob
//(repo/files API,2026-07 ) README.md / .gitattributes / configuration.json
//( ModelScope ,MLX )Gemma 4 3n : tokenizer.model /
// special_tokens_map.json,tokenizer_config.json (chat template chat_template.jinja),
// processor_config.json mlx-swift-lm ,
return [
ModelFile(path: "config.json", bytes: 107_207),
ModelFile(path: "generation_config.json", bytes: 215),
ModelFile(path: "model.safetensors", bytes: 2_507_515_399),
ModelFile(path: "model.safetensors.index.json", bytes: 129_688),
ModelFile(path: "special_tokens_map.json", bytes: 769),
ModelFile(path: "tokenizer.json", bytes: 33_442_553),
ModelFile(path: "tokenizer.model", bytes: 4_696_020),
ModelFile(path: "tokenizer_config.json", bytes: 1_202_305),
ModelFile(path: "chat_template.jinja", bytes: 1_626),
ModelFile(path: "config.json", bytes: 5_996),
ModelFile(path: "generation_config.json", bytes: 208),
ModelFile(path: "model.safetensors", bytes: 3_581_101_896),
ModelFile(path: "model.safetensors.index.json", bytes: 230_329),
ModelFile(path: "tokenizer.json", bytes: 32_169_626),
ModelFile(path: "tokenizer_config.json", bytes: 2_095),
ModelFile(path: "chat_template.jinja", bytes: 17_336),
ModelFile(path: "processor_config.json", bytes: 902),
]
case .vl:
// Qwen3-VL-4B-Instruct-4bit: mlx-community blob
@@ -90,15 +99,35 @@ nonisolated enum ModelManifest {
files(for: kind).reduce(0) { $0 + $1.bytes }
}
/// URL MNN / MLX `resolve/master`;
/// `.vl` 退()
/// **** URL(= ) `resolve/master`
/// `fileURLs` + hf-mirror 退;
/// ()
static func fileURL(for kind: ModelKind, file: ModelFile) -> URL {
fileURLs(for: kind, file: file)[0]
}
/// :**(ModelScope,resolve/master),hf-mirror(resolve/main)退**
/// mlx-community , `.part`
/// ( `Range: bytes=offset-` ,): + hf-mirror 退
/// `.vl`( / hf 退) `baseURL`()
static func fileURLs(for kind: ModelKind, file: ModelFile) -> [URL] {
var urls: [URL] = []
if let repo = modelScopeRepo(for: kind) {
return URL(string: "https://modelscope.cn/models/\(repo)/resolve/master/")!
.appendingPathComponent(file.path)
urls.append(
URL(string: "https://modelscope.cn/models/\(repo)/resolve/master/")!
.appendingPathComponent(file.path))
}
return baseURL
.appendingPathComponent(kind.rawValue, isDirectory: true)
.appendingPathComponent(file.path)
if let hfRepo = huggingFaceMirrorRepo(for: kind) {
urls.append(
URL(string: "https://hf-mirror.com/\(hfRepo)/resolve/main/")!
.appendingPathComponent(file.path))
}
if urls.isEmpty {
urls.append(
baseURL
.appendingPathComponent(kind.rawValue, isDirectory: true)
.appendingPathComponent(file.path))
}
return urls
}
}

View File

@@ -2,19 +2,19 @@ import Foundation
nonisolated enum ModelKind: String, CaseIterable {
/// Models/ / CDN
/// Qwen3.5-2B **Gemma-3n E2B( 4bit, MLX/GPU)**:
/// - llm:****MLX , `gemma3n` (mlx-swift-lm )
/// Gemma-3n MLX (VLMModelFactory gemma3n), OCR + LLM
/// Qwen3.5-2B Gemma-3n **Gemma 4 E2B( 4bit, MLX/GPU)**:
/// - llm:****MLX , LLMModelFactory `gemma4`
/// (mlx-swift-lm 3.31.4 ;`Gemma4Model` `language_model` ,/)
/// e2b ( gemma4_unified 12B), OCR + LLM
/// - vl:, switch,/
/// - mnnLLM:Gemma-3n MNN( + MatFormer MNN ),
/// MNN/SME2 ;,/
case llm = "gemma-3n-E2B-it-lm-4bit"
/// - mnnLLM: MNN/SME2 ;,/
case llm = "gemma-4-e2b-it-4bit"
case vl = "Qwen3-VL-4B-Instruct-4bit"
case mnnLLM = "Qwen3.5-2B-MNN"
var displayName: String {
switch self {
case .llm: return "Gemma-3n E2B (MLX)"
case .llm: return "Gemma 4 E2B (MLX)"
case .vl: return "Qwen3-VL-4B"
case .mnnLLM: return "Qwen3.5-2B (MNN/SME2)"
}
@@ -27,7 +27,7 @@ nonisolated enum ModelKind: String, CaseIterable {
var sentinelFilename: String { "config.json" }
/// : / /
/// Gemma-3n E2B(MLX,).vl/.mnnLLM ,
/// Gemma 4 E2B(MLX,).vl/.mnnLLM ,
static let userFacing: [ModelKind] = [.llm]
}

View File

@@ -116,8 +116,8 @@ JSON schema(严格):
// MARK: - (OCR ParsedReport, LLM)
/// C2/ : Gemma-3n (MLX gemma3n ),
/// Vision OCR , LLM meta +
/// C2/ : Gemma 4 E2B (e2b ,
/// gemma4_unified 12B), Vision OCR , LLM meta +
/// reportExtraction() schema, source_page / source_box OCR ,
/// ,
static func reportExtractionFromText(_ ocrText: String, today: Date = .now) -> String {

View File

@@ -2,6 +2,10 @@ import Foundation
import MLX
import MLXVLM
import MLXLMCommon
// 3.x HF Hub/ opt-in ( LLMSession )
import MLXHuggingFace
import HuggingFace
import Tokenizers
/// MLX VL (Qwen3-VL)
/// LLMSession actor , AIRuntime
@@ -25,8 +29,11 @@ actor VLSession {
/// VL ( config.json + weights + tokenizer + processor)
static func load(folderURL: URL) async throws -> VLSession {
let configuration = ModelConfiguration(directory: folderURL)
// 3.31.4: LLMSession, Downloader()+ HF
let container = try await withDeviceOverride {
try await VLMModelFactory.shared.loadContainer(
from: #hubDownloader(),
using: #huggingFaceTokenizerLoader(),
configuration: configuration
)
}

View File

@@ -1,7 +1,7 @@
import SwiftUI
/// : Gemma-3n( 4bit),** MLX/GPU**
/// MNN/SME2 LLM (Gemma-3n MNN ),
/// : Gemma 4( 4bit),** MLX/GPU**
/// MNN/SME2 LLM (Gemma 4 MNN ),
/// , + + AI
struct InferenceSettingsView: View {
// AI(Gemini) key CloudAI ,@AppStorage
@@ -11,7 +11,7 @@ struct InferenceSettingsView: View {
/// , push
@State private var showSelfTest = false
/// (Gemma-3n,MLX)
/// (Gemma 4,MLX)
private var modelReady: Bool {
modelService.states[.llm]?.phase == .ready
}
@@ -41,7 +41,7 @@ struct InferenceSettingsView: View {
.onAppear { modelService.refreshStates() }
}
/// (,):MLX · Metal GPU, Gemma-3n E2B
/// (,):MLX · Metal GPU, Gemma 4 E2B
private var localEngineCard: some View {
HStack(spacing: 12) {
ZStack {
@@ -56,7 +56,7 @@ struct InferenceSettingsView: View {
Text("MLX · GPU")
.font(.tjScaled(15, weight: .semibold))
.foregroundStyle(Tj.Palette.text)
Text("Metal GPU · 端侧推理 Gemma-3n E2B")
Text("Metal GPU · 端侧推理 Gemma 4 E2B")
.font(.tjScaled(12))
.foregroundStyle(Tj.Palette.text3)
.lineLimit(2)
@@ -141,7 +141,7 @@ struct InferenceSettingsView: View {
Text("启用云端增强")
.font(.tjScaled(15, weight: .semibold))
.foregroundStyle(Tj.Palette.text)
Text("默认端侧 Gemma-3n;开启后「读报告原图 / 深度解读 / 多语言」走 Google Gemini。")
Text("默认端侧 Gemma 4;开启后「读报告原图 / 深度解读 / 多语言」走 Google Gemini。")
.font(.tjScaled(12))
.foregroundStyle(Tj.Palette.text3)
}
@@ -166,7 +166,7 @@ struct InferenceSettingsView: View {
.font(.tjScaled(12))
.foregroundStyle(Tj.Palette.text3)
}
Text("Key 仅存本机;断网或额度用尽时自动回退端侧 Gemma-3n,功能不中断。")
Text("Key 仅存本机;断网或额度用尽时自动回退端侧 Gemma 4,功能不中断。")
.font(.tjScaled(11))
.foregroundStyle(Tj.Palette.text3)
}
@@ -177,7 +177,7 @@ struct InferenceSettingsView: View {
}
private var noteCard: some View {
Text("隐私优先:默认端侧 Gemma-3n(MLX · Metal GPU)推理,数据不出设备;仅在你开启「云端 AI」后,深度任务才走 Google Gemini。切换后下一次 AI 调用生效。")
Text("隐私优先:默认端侧 Gemma 4(MLX · Metal GPU)推理,数据不出设备;仅在你开启「云端 AI」后,深度任务才走 Google Gemini。切换后下一次 AI 调用生效。")
.font(.tjScaled(12))
.foregroundStyle(Tj.Palette.text3)
.frame(maxWidth: .infinity, alignment: .leading)

View File

@@ -167,7 +167,7 @@ struct MeView: View {
.buttonStyle(.plain)
}
/// MLX/GPU(Gemma-3n E2B);MNN/SME2 LLM
/// MLX/GPU(Gemma 4 E2B);MNN/SME2 LLM
private var engineDetail: String { "MLX · GPU" }
private var languageCard: some View {

View File

@@ -141,7 +141,7 @@ struct ModelManagementView: View {
} else if allReady {
HStack(spacing: 6) {
Image(systemName: "checkmark.seal.fill")
Text("Gemma-3n E2B 已就绪")
Text("Gemma 4 E2B 已就绪")
}
.font(.tjScaled( 13, weight: .semibold))
.foregroundStyle(Tj.Palette.leaf)

View File

@@ -1,6 +1,6 @@
import SwiftUI
/// : prompt,(MLX·GPU,Gemma-3n E2B)
/// : prompt,(MLX·GPU,Gemma 4 E2B)
/// prefill / decode , (§12 2/6)
struct ModelSelfTestView: View {
@State private var output = ""
@@ -177,7 +177,7 @@ struct ModelSelfTestView: View {
}
}
}
Text("Gemma-3n E2B 在端侧 MLX(Metal GPU)推理,100% 本地、不上云。")
Text("Gemma 4 E2B 在端侧 MLX(Metal GPU)推理,100% 本地、不上云。")
.font(.tjScaled( 10))
.foregroundStyle(Tj.Palette.text3)
}

View File

@@ -1743,46 +1743,46 @@
}
}
},
"Gemma-3n E2B 在端侧 MLX(Metal GPU)推理,100% 本地、不上云。" : {
"Gemma 4 E2B 在端侧 MLX(Metal GPU)推理,100% 本地、不上云。" : {
"localizations" : {
"en" : {
"stringUnit" : {
"state" : "translated",
"value" : "Gemma-3n E2B runs on-device with MLX (Metal GPU) — 100% local, nothing goes to the cloud."
"value" : "Gemma 4 E2B runs on-device with MLX (Metal GPU) — 100% local, nothing goes to the cloud."
}
},
"ja" : {
"stringUnit" : {
"state" : "translated",
"value" : "Gemma-3n E2B は端末上の MLX(Metal GPU)で推論します。100% ローカルで、クラウドには送信しません。"
"value" : "Gemma 4 E2B は端末上の MLX(Metal GPU)で推論します。100% ローカルで、クラウドには送信しません。"
}
},
"ko" : {
"stringUnit" : {
"state" : "translated",
"value" : "Gemma-3n E2B는 기기 내 MLX(Metal GPU)에서 추론합니다. 100% 로컬이며 클라우드로 전송하지 않습니다."
"value" : "Gemma 4 E2B는 기기 내 MLX(Metal GPU)에서 추론합니다. 100% 로컬이며 클라우드로 전송하지 않습니다."
}
}
}
},
"Gemma-3n E2B 已就绪" : {
"Gemma 4 E2B 已就绪" : {
"localizations" : {
"en" : {
"stringUnit" : {
"state" : "translated",
"value" : "Gemma-3n E2B is ready"
"value" : "Gemma 4 E2B is ready"
}
},
"ja" : {
"stringUnit" : {
"state" : "translated",
"value" : "Gemma-3n E2B の準備ができました"
"value" : "Gemma 4 E2B の準備ができました"
}
},
"ko" : {
"stringUnit" : {
"state" : "translated",
"value" : "Gemma-3n E2B 준비 완료"
"value" : "Gemma 4 E2B 준비 완료"
}
}
}
@@ -1790,24 +1790,24 @@
"hi" : {
},
"Key 仅存本机;断网或额度用尽时自动回退端侧 Gemma-3n,功能不中断。" : {
"Key 仅存本机;断网或额度用尽时自动回退端侧 Gemma 4,功能不中断。" : {
"localizations" : {
"en" : {
"stringUnit" : {
"state" : "translated",
"value" : "Your key is stored only on this device; if you're offline or out of quota, it automatically falls back to on-device Gemma-3n, so features keep working."
"value" : "Your key is stored only on this device; if you're offline or out of quota, it automatically falls back to on-device Gemma 4, so features keep working."
}
},
"ja" : {
"stringUnit" : {
"state" : "translated",
"value" : "キーは本体にのみ保存されます。オフライン時や利用枠を使い切った場合は自動で端末上の Gemma-3n に切り替わり、機能は途切れません。"
"value" : "キーは本体にのみ保存されます。オフライン時や利用枠を使い切った場合は自動で端末上の Gemma 4 に切り替わり、機能は途切れません。"
}
},
"ko" : {
"stringUnit" : {
"state" : "translated",
"value" : "키는 기기에만 저장됩니다. 오프라인이거나 할당량을 모두 사용하면 자동으로 기기 내 Gemma-3n으로 전환되어 기능이 중단되지 않습니다."
"value" : "키는 기기에만 저장됩니다. 오프라인이거나 할당량을 모두 사용하면 자동으로 기기 내 Gemma 4으로 전환되어 기능이 중단되지 않습니다."
}
}
}
@@ -1818,24 +1818,24 @@
"lo" : {
},
"Metal GPU · 端侧推理 Gemma-3n E2B" : {
"Metal GPU · 端侧推理 Gemma 4 E2B" : {
"localizations" : {
"en" : {
"stringUnit" : {
"state" : "translated",
"value" : "Metal GPU · On-device inference with Gemma-3n E2B"
"value" : "Metal GPU · On-device inference with Gemma 4 E2B"
}
},
"ja" : {
"stringUnit" : {
"state" : "translated",
"value" : "Metal GPU · 端末上で Gemma-3n E2B を推論"
"value" : "Metal GPU · 端末上で Gemma 4 E2B を推論"
}
},
"ko" : {
"stringUnit" : {
"state" : "translated",
"value" : "Metal GPU · 기기 내 Gemma-3n E2B 추론"
"value" : "Metal GPU · 기기 내 Gemma 4 E2B 추론"
}
}
}
@@ -7231,24 +7231,24 @@
}
}
},
"已停用:Gemma-3n 无 MNN 转换模型,统一走 MLX" : {
"已停用:Gemma 4 无 MNN 转换模型,统一走 MLX" : {
"localizations" : {
"en" : {
"stringUnit" : {
"state" : "translated",
"value" : "Disabled: Gemma-3n has no MNN-converted model, so it runs on MLX"
"value" : "Disabled: Gemma 4 has no MNN-converted model, so it runs on MLX"
}
},
"ja" : {
"stringUnit" : {
"state" : "translated",
"value" : "無効Gemma-3n には MNN 変換モデルがないため、MLX で統一します"
"value" : "無効Gemma 4 には MNN 変換モデルがないため、MLX で統一します"
}
},
"ko" : {
"stringUnit" : {
"state" : "translated",
"value" : "사용 중지: Gemma-3n은 MNN 변환 모델이 없어 MLX로 통일합니다"
"value" : "사용 중지: Gemma 4은 MNN 변환 모델이 없어 MLX로 통일합니다"
}
}
}
@@ -19327,24 +19327,24 @@
}
}
},
"隐私优先:默认端侧 Gemma-3n(MLX · Metal GPU)推理,数据不出设备;仅在你开启「云端 AI」后,深度任务才走 Google Gemini。切换后下一次 AI 调用生效。" : {
"隐私优先:默认端侧 Gemma 4(MLX · Metal GPU)推理,数据不出设备;仅在你开启「云端 AI」后,深度任务才走 Google Gemini。切换后下一次 AI 调用生效。" : {
"localizations" : {
"en" : {
"stringUnit" : {
"state" : "translated",
"value" : "Privacy first: by default, inference runs on-device with Gemma-3n (MLX · Metal GPU) and your data never leaves the device; only after you turn on \"Cloud AI\" do deep tasks use Google Gemini. The change takes effect on your next AI call."
"value" : "Privacy first: by default, inference runs on-device with Gemma 4 (MLX · Metal GPU) and your data never leaves the device; only after you turn on \"Cloud AI\" do deep tasks use Google Gemini. The change takes effect on your next AI call."
}
},
"ja" : {
"stringUnit" : {
"state" : "translated",
"value" : "プライバシー優先:既定では端末内の Gemma-3nMLX・Metal GPUで推論し、データは端末外に出ません。「クラウド AI」をオンにした場合のみ、高度なタスクが Google Gemini を使います。切り替えは次回の AI 呼び出しから有効になります。"
"value" : "プライバシー優先:既定では端末内の Gemma 4MLX・Metal GPUで推論し、データは端末外に出ません。「クラウド AI」をオンにした場合のみ、高度なタスクが Google Gemini を使います。切り替えは次回の AI 呼び出しから有効になります。"
}
},
"ko" : {
"stringUnit" : {
"state" : "translated",
"value" : "개인정보 우선: 기본적으로 기기 내 Gemma-3n(MLX · Metal GPU)으로 추론하며 데이터는 기기를 벗어나지 않아요. \"클라우드 AI\"를 켠 경우에만 심층 작업이 Google Gemini를 사용해요. 전환은 다음 AI 호출부터 적용돼요."
"value" : "개인정보 우선: 기본적으로 기기 내 Gemma 4(MLX · Metal GPU)으로 추론하며 데이터는 기기를 벗어나지 않아요. \"클라우드 AI\"를 켠 경우에만 심층 작업이 Google Gemini를 사용해요. 전환은 다음 AI 호출부터 적용돼요."
}
}
}
@@ -19638,24 +19638,24 @@
}
}
},
"默认端侧 Gemma-3n;开启后「读报告原图 / 深度解读 / 多语言」走 Google Gemini。" : {
"默认端侧 Gemma 4;开启后「读报告原图 / 深度解读 / 多语言」走 Google Gemini。" : {
"localizations" : {
"en" : {
"stringUnit" : {
"state" : "translated",
"value" : "On-device Gemma-3n by default; once enabled, \"read report images / deep interpretation / multilingual\" use Google Gemini."
"value" : "On-device Gemma 4 by default; once enabled, \"read report images / deep interpretation / multilingual\" use Google Gemini."
}
},
"ja" : {
"stringUnit" : {
"state" : "translated",
"value" : "既定は端末内の Gemma-3n。オンにすると「レポート原本の読み取り/深い解釈/多言語」が Google Gemini を使います。"
"value" : "既定は端末内の Gemma 4。オンにすると「レポート原本の読み取り/深い解釈/多言語」が Google Gemini を使います。"
}
},
"ko" : {
"stringUnit" : {
"state" : "translated",
"value" : "기본은 기기 내 Gemma-3n. 켜면 \"리포트 원본 읽기 / 심층 해석 / 다국어\"가 Google Gemini를 사용해요."
"value" : "기본은 기기 내 Gemma 4. 켜면 \"리포트 원본 읽기 / 심층 해석 / 다국어\"가 Google Gemini를 사용해요."
}
}
}

View File

@@ -28,7 +28,7 @@ final class HealthExport {
var inferredLabelCN: String?
// demo
/// tag, "gemma-3n-E2B-it-lm-4bit"(MLX )
/// tag, "gemma-4-e2b-it-4bit"(MLX )
var modelTag: String
/// tok/s, demo #6 Live Activity
var decodeRate: Double
@@ -44,7 +44,7 @@ final class HealthExport {
inferredTimeToDate: Date? = nil,
inferredIntent: String? = nil,
inferredLabelCN: String? = nil,
modelTag: String = "gemma-3n-E2B-it-lm-4bit",
modelTag: String = "gemma-4-e2b-it-4bit",
decodeRate: Double = 0) {
self.prompt = prompt
self.content = content

View File

@@ -178,7 +178,7 @@ actor CaptureService {
///
/// hybrid :
/// - **(Gemini)**: Gemini (·VL, source_box ),
/// OCR Gemma-3n(MLX ,)
/// OCR Gemma 4(MLX ,)
/// - **线/**:退 Vision OCR(,<1s/) Gemma LLM meta+
/// (线//)退,(§3.2 退线)
private func runVL(on assets: [FileVault.SavedAsset]) async throws -> ParsedReport {

View File

@@ -88,17 +88,38 @@ final class ModelDownloadService {
if Task.isCancelled { return }
let destination = store.fileURL(for: kind, relativePath: file.path)
let base = completedBefore
try await downloader.download(
from: ModelManifest.fileURL(for: kind, file: file),
to: destination,
expectedBytes: file.bytes,
onProgress: { [weak self] received in
guard let self else { return }
Task { @MainActor in
self.applyProgress(kind, currentTotal: base + received)
}
// + hf-mirror 退:,;
// .part(,Range 穿)
let sources = ModelManifest.fileURLs(for: kind, file: file)
var lastError: Error?
var downloaded = false
for (idx, source) in sources.enumerated() {
if Task.isCancelled { return }
do {
try await downloader.download(
from: source,
to: destination,
expectedBytes: file.bytes,
onProgress: { [weak self] received in
guard let self else { return }
Task { @MainActor in
self.applyProgress(kind, currentTotal: base + received)
}
}
)
downloaded = true
break
} catch {
lastError = error
#if DEBUG
let more = idx + 1 < sources.count ? ",回退下一个源" : ""
print("[ModelDownload] \(file.path)\(idx + 1)/\(sources.count) 失败:"
+ "\(error.localizedDescription)\(more)")
#endif
}
)
}
if !downloaded { throw lastError ?? DownloadError.badStatus(0) }
completedBefore += file.bytes
}
finish(kind, success: true, message: nil)