mirror of
https://github.com/wu736139669/hapi.git
synced 2026-11-02 23:51:34 +00:00
Part 1 — Simplified Chinese localization of the iOS app layer: - ios/Hapi/Resources/Localizable.xcstrings: hand-authored String Catalog (395 keys, zh-Hans; en implicit as source). Terminology mined from web/src/lib/locales/zh-CN.ts (会话/新建会话/权限模式/允许/拒绝/工作树/ 机器/语音输入/用量/草稿夹 …). - Mechanical edits only: merged multi-part string concatenations into single LocalizedStringKey literals, converted ternary/plain-String user copy to String(localized:), switched helper params (detailRow, DashboardCard, optionPicker, notice) to LocalizedStringKey. - LocalizedNoticeMapper (app layer): display-point translation of the 27 known HapiKit-emitted strings (ChatInteractor notices, dictation errors, window-sync warnings, files fallbacks, worktree-name validation) with verbatim passthrough for server-originated text; package stays language-free. - Deliberately untranslated (web parity / non-copy): event rows (EventPresentation — web renders presentation.ts verbatim), catalog option labels (Default/Auto/Sonnet/permission modes — web shows them verbatim), tool names, code-like titles (grep(pattern:), MCP:, Skill, Task), unit suffixes (B/KB/MB, m/h/d/w), decorative separators. - Language row wired: AppLanguage gains .system (follow system, new default); explicit picks write the AppleLanguages override, Follow system removes it; footer notes a relaunch applies it (no supported in-place SwiftUI locale swap). - project.pbxproj: zh-Hans added to knownRegions. Part 2 — #39 catalog dedupe (HapiProtocol): - NewSessionCatalogs.claudeModels/claudeEfforts now derive from ClaudeModels/ClaudeEfforts (single source shared with ModelCatalog); codexReasoningEfforts stays own data (web CODEX_REASONING_EFFORT_OPTIONS minus max); effortLabel delegates to ModelCatalog.capitalizedFirst. - New CatalogTests lock test for the derived option lists. Gate: bash ios/scripts/linux-test.sh — 459 tests green.
95 lines
3.5 KiB
Swift
95 lines
3.5 KiB
Swift
import HapiProtocol
|
|
import SwiftUI
|
|
|
|
/// Manual pairing path for hubs without `--relay` (nothing to scan): type or
|
|
/// paste the hub URL and the access token the hub prints at startup, then
|
|
/// continue into the shared confirm step.
|
|
struct ManualEntryView: View {
|
|
@Environment(\.dismiss) private var dismiss
|
|
@State private var hubUrl = ""
|
|
@State private var accessToken = ""
|
|
@State private var pending: PendingPairing?
|
|
|
|
private var canContinue: Bool {
|
|
!hubUrl.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty
|
|
&& !accessToken.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty
|
|
}
|
|
|
|
var body: some View {
|
|
NavigationStack {
|
|
Form {
|
|
Section {
|
|
TextField("http://192.168.1.20:3006", text: $hubUrl)
|
|
.keyboardType(.URL)
|
|
.textContentType(.URL)
|
|
.textInputAutocapitalization(.never)
|
|
.autocorrectionDisabled()
|
|
} header: {
|
|
Text("Hub URL")
|
|
} footer: {
|
|
Text("The address the hub prints at startup — a LAN address like http://192.168.1.20:3006, or the public tunnel URL when running with --relay.")
|
|
}
|
|
|
|
Section {
|
|
TextField("Access token", text: $accessToken)
|
|
.textInputAutocapitalization(.never)
|
|
.autocorrectionDisabled()
|
|
.monospaced()
|
|
} header: {
|
|
Text("Access token")
|
|
} footer: {
|
|
Text("Printed by the hub at startup, and shown in the web app under Settings → Companion Pairing. Pasting a full pairing link into either field also works.")
|
|
}
|
|
|
|
Section {
|
|
Button("Continue") {
|
|
continueToConfirm()
|
|
}
|
|
.frame(maxWidth: .infinity)
|
|
.disabled(!canContinue && parsePastedLink() == nil)
|
|
}
|
|
}
|
|
.navigationTitle("Enter Hub Details")
|
|
.navigationBarTitleDisplayMode(.inline)
|
|
.toolbar {
|
|
ToolbarItem(placement: .cancellationAction) {
|
|
Button("Cancel") {
|
|
dismiss()
|
|
}
|
|
}
|
|
}
|
|
.navigationDestination(item: $pending) { pending in
|
|
PairingConfirmView(pending: pending)
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Convenience: a whole pairing link pasted into either field carries
|
|
/// both values at once.
|
|
private func parsePastedLink() -> BindLink? {
|
|
BindLink.parse(hubUrl) ?? BindLink.parse(accessToken)
|
|
}
|
|
|
|
private func continueToConfirm() {
|
|
if let link = parsePastedLink() {
|
|
pending = PendingPairing(
|
|
hubUrl: link.hubUrl,
|
|
accessToken: link.accessToken,
|
|
source: .manual
|
|
)
|
|
return
|
|
}
|
|
var address = hubUrl.trimmingCharacters(in: .whitespacesAndNewlines)
|
|
// Typing the scheme on a phone is annoying; local hubs serve plain
|
|
// HTTP, and the confirm step shows the resulting URL before pairing.
|
|
if !address.isEmpty, !address.contains("://") {
|
|
address = "http://\(address)"
|
|
}
|
|
pending = PendingPairing(
|
|
hubUrl: address,
|
|
accessToken: accessToken.trimmingCharacters(in: .whitespacesAndNewlines),
|
|
source: .manual
|
|
)
|
|
}
|
|
}
|