mirror of
https://github.com/wu736139669/hapi.git
synced 2026-10-15 20:29:55 +00:00
bc9df82dc6e24140a4c76dfd6a86c0e53df9f8d2
7
Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
6557dda7f6 |
feat: native agent brand icons on both platforms
Port web's per-agent brand icons (web/src/components/AgentFlavorIcon.tsx, brand SVGs via @lobehub/icons v5.4.0) to Android and iOS — device feedback: neither native app showed agent icons. Android: 10 VectorDrawables (ic_agent_*) + AgentFlavorIcon composable (color variants render untinted Image; monos tint via LocalContentColor; copilot fixed #24292F/#E6EDF3; unknown -> "Un" badge) with light/dark previews. Codex/gemini keep their real gradients via aapt attrs; Antigravity's blurred-blob wing (SVG filters, unportable) is approximated with a green-yellow-red-blue linear gradient over the wing path. iOS: Assets.xcassets/AgentIcons imagesets (SVG, preserves-vector; monos template-intent) + AgentFlavorIconView. SVGs stay paths-only for Xcode's rasterizer, so gradient marks flatten: codex glyph -> #7A9DFF (middle stop), gemini -> #3186FF base star, agy -> #3186FF wing. Wired to match web placements on both apps: session-list row (icon before title; flavor label leaves the meta line), chat header meta line (icon + label), new-session agent picker (chip leadingIcon / Label icon). Verified: gradle :app:testDebugUnitTest :app:assembleDebug green; iOS Contents.json/SVGs machine-validated, swiftc -parse clean, geometry eyeballed via rendered contact sheet (app target still compile-unverified on Linux). |
||
|
|
6066f20c20 |
feat(ios): zh-CN localization + catalog dedupe (A-M5)
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. |
||
|
|
5773ca2d93 |
merge: A-M4b iOS scratchlist
# Conflicts: # ios/Hapi/Features/Chat/ChatModel.swift # ios/Hapi/Features/Chat/ChatView.swift # ios/README.md |
||
|
|
6c7746d2b0 |
feat(ios): scratchlist (A-M4b)
Per-session parked notes mirroring the Android B-M4d feature: - HapiProtocol Models/ScratchlistApi.swift: wire types for entries CRUD (idempotent create via client entryId+createdAt), attachment metadata, limits (defaults from shared/src/scratchlistAttachments.ts), upload envelope, and the typed error codes (scratchlist_at_cap, scratchlist_attachment_too_large, scratchlist_attachment_in_use, ...). - Endpoints/ScratchlistEndpoints.swift: GET/POST/PUT/DELETE entries, limits, base64 upload, raw-bytes attachment fetch, attachment delete. - Stores/ScratchlistStore.swift: @MainActor @Observable per-session cache behind the SessionScratchlistStoring seam - open/release observation, 16 ms-coalesced refetch on the scratchlistUpdatedAt SSE signal, optimistic create/update/delete with surgical entryId reconcile + rollback (refresh preserves in-flight optimistic creates), 200-entry cap pre-check + hub 409 verdict, uploadsInFlight, cached limits with offline defaults, UTF-16 text clamp at 10000. - Stores/ScratchlistAttachmentGuard.swift: pure Fits/Downscale/Reject budget verdicts ported verbatim. - SessionListStore: onScratchlistInvalidation callback fired when a session patch carries scratchlistUpdatedAt (the seam the M4b comment reserved); HubSession wires it into the store and injects the store into ChatInteractor. - ChatInteractor (additive tail section): scratchlist store property, scratchlistCount badge seam, insertComposerText, parkComposerDraft (composer clears only after the hub accepts; at-cap/failed keep the draft). - Features/Scratchlist/: sheet off the chat toolbar's note icon with count badge - entry cards (4-line preview, relative age, authed thumbnails via NSCache loader, filename chips), edit sheet (PhotosPicker -> guard -> JPEG downscale -> upload spinner tile, remove, delete/save), full-screen viewer (GeneratedImage pattern), per-entry To composer, and Park current draft in the screen header (a deliberate placement divergence from Android's composer button - the composer UI is owned by the concurrent attachments package). iOS-only import step transcodes disallowed-but-decodable rasters (HEIC) to JPEG before the guard. - Tests (36, all transcribed from or mirroring the Android suites): store CRUD optimistic/rollback/at-cap/invalidation/upload/limits with canonical wire-body asserts + the SessionListStore seam test, 9 guard verdicts, 9 interactor park/insert/badge seam tests over a fake store. Verified on Linux via a dockerized Swift 6.0 scratch copy (FIFO HTTP performer): full HapiClient build under strict concurrency, 36/36 new tests green, SessionListStore 17/17 and ChatInteractor 29/29 suites green; app-side screen model + loader typechecked against stub frameworks; SwiftUI views and the CG-based import parse-checked (macOS CI compiles them). |
||
|
|
05d063cc33 |
feat(ios): composer attachments + dictation (A-M3f)
Attachments (Android B-M3f semantics ported verbatim): - HapiClient/Attachments/AttachmentPolicy — pure plan matrix (>4 MB recompressible image -> 2048 px JPEG q85 with .jpg rename, 50 MB hard reject, 192 MB image read cap, 512 px q80 previewUrl data-URL thumbs, data-URL parse/round-trip). - HapiClient/Attachments/ComposerAttachments — upload-on-pick tray over an AttachmentUploading seam (APIClient conforms): uploading/ready/failed chips, retained payload for retry, remove -> best-effort delete, mid-upload removal deletes the orphan on completion, consume() -> AttachmentMetadata with JPEG data-URL previewUrl, discardAllDetached + deinit orphan cleanup (Android onCleared analogue). - ChatInteractor: tray ownership, unsettled chips refuse the send with a notice, attachments-only sends post empty text, optimistic rows carry the metadata, appendDictatedText/postNotice/discardAttachments. - App: AttachmentPreparer (capped security-scoped reads, ImageIO downscale/encode with EXIF transform, HEIC-undecodable fallback), PhotosPicker multi (videos via FileRepresentation temp files), UIImagePickerController camera capture, fileImporter; composer chip row (thumb/spinner/tap-to-retry/remove) with attachment-aware send gating; user bubbles upgrade chips to off-main-decoded previewUrl thumbnails (web-sent attachments included). Dictation (Android B-M3ce port): - HapiProtocol/Models/VoiceApi — TranscriptionResponse, TranscriptionProvidersResponse, TranscriptionProviderInfo. - HapiClient/Endpoints/VoiceEndpoints — GET /api/voice/transcription/providers + multipart POST /api/voice/transcription (file/provider/mode/language, Android part order) over MultipartFormData; DictationTranscribing conformance. - HapiClient/Voice/DictationController — idle/starting/recording/ transcribing, transcribed/noProvider/error events, provider memoized (first standard-capable entry), appendTranscript port. - App: AVAudioRecorderDictation (m4a/AAC mono 44.1 kHz 96 kbps, session activate/deactivate), mic button + recording chip (elapsed + cancel), record-permission request via AVAudioApplication. Info.plist gains NSMicrophoneUsageDescription; the camera string now covers attachment capture (modern PhotosPicker needs no photo-library permission). Tests: policy matrix, tray over the real client with exact base64 upload bodies + gated in-flight scenarios, dictation controller suite with fake recorder/transport, voice endpoint request shapes, and the interactor attachment-send matrix transcribed from the Android VM tests (wire bodies byte-for-byte). |
||
|
|
eed3dddb94 |
feat(ios): composer, permission actions, session config (A-M3ab)
Interaction layer turning the read-only chat into a working remote control,
mirroring the merged Android B-M3ab feature-for-feature (web authority where
the ports disagreed):
- ChatInteractor (HapiClient, fully swift-test covered): optimistic composer
sends (appendOptimistic -> POST -> status settle), queue-by-default with a
long-press Send&Steer intent while a turn is active, tap-to-retry on failed
rows (steer retries degrade to queue), per-session drafts
(UserDefaultsChatDrafts, hub-scoped keys, debounced + flushed on close).
- session_inactive (409) recovery: one POST /resume (current permissionMode)
then retry; a superseding session id seeds the new window
(MessageWindowControllers.seed), migrates the draft, retargets the
optimistic row and emits sessionSuperseded -- ChatView/HomeView replace the
navigation entry in place.
- Queued bar: uninvoked sends in web sort order with Cancel (optimistic
DELETE; invoked-race ingests the authoritative row as sent), Edit
(cancel + composer prefill, newer-draft guard) and Steer (invoked answers
reconcile a missed consume); single-flight per-row op guard.
reconcileQueuedState now runs on chat open and on session-pipe gap.
- Permission actions: flavor-exact bodies mirroring PermissionFooter.tsx --
claude {} / allowTools (Bash(cmd)) / mode:acceptEdits, codex-family
decision approved / approved_for_session / abort via deny -- plus
AskUserQuestion flat answers (option cards, Other free text, no-questions
fallback, cursor stable ids) and request_user_input nested answers
(user_note suffix, required validation); optimistic Resolving /
AlreadyHandled (404/409) overrides settled by the agentState patch.
ChatPipeline now re-attaches the window row's client status so failed
user rows actually render the retry affordance (web normalize.ts parity;
the Android reference misses this overlay).
- Session config sheet (toolbar gear): catalog-driven permission-mode picker
with tones, claude static model/effort catalogs (ModelCatalog port), codex
models via new GET /sessions/:id/codex-models endpoint + wire types with
per-model reasoning efforts; optimistic detail updates
(SessionListStore.updateDetailLocal, new) rolled forward to server truth
on error.
- Lifecycle: VisibilityReporter posts POST /api/visibility per tracked
handshake subscriptionId on scene-phase flips (404 prunes); ChatSession
exposes its subscriptionId and feeds the reporter; the global SSE pipe was
already HubSession-lifetime on iOS.
- Tests: ChatInteractorTests transcribes the Android interaction suite
against the real APIClient/AuthManager/SessionListStore/window registry
with only HTTP scripted -- canonical JSON bodies asserted byte-for-byte
(send/approve/deny/config), optimistic send happy/fail/retry, 409 resume
both id paths, cancel invoked-race, steer reconcile, edit prefill,
override lifecycle, config optimistic + rollback, drafts, abort.
|
||
|
|
a4355d1837 |
feat(ios): read-only chat assembly (A-M2f)
Wires the merged M2 pieces into a usable pair -> list -> live chat flow: - pbxproj: link HapiUI into the app target (UUIDs C7/C8, mirroring the existing HapiProtocol/HapiClient product references; only project change). - HubSession: MessageWindowControllers registry + per-chat ChatSession factory with in-memory per-session SSE resume cursors and scene-phase forwarding to the active chat. - ChatSession (app): session-scope SSEClient while the chat is open; window opened/activated before subscribing; a single consume task awaits every event into the window actor, giving Android-channel-equivalent arrival-order ingestion; session-updated and friends route through the shared SyncEventRouter (detail patching), session-removed clears the window, a gap handshake triggers full resync + detail refetch + catch-up tail sync; stop hands the cursor back for seamless reopen. - ChatPipeline (HapiClient): actor running the reduction path off-main - queued-row filter, normalization memoized by row instance identity, reduce + buildVisibleChatBlocks with previousGroups-stable group ids. - ChatModel: window state + detail agentState + machine labels -> serial ~100 ms-coalesced pipeline loop (first run immediate, publishes strictly ordered), header title cascade, loading/empty/error states, loadOlder / retry, last-seen stamping on every update. - ChatView: bottom-anchored ScrollView/LazyVStack (defaultScrollAnchor + scrollPosition(id:)), auto-stick at bottom, new-messages pill, top sentinel paging with documented scroll re-anchoring, degraded banners. - Block views: user bubble w/ attachment chips, agent markdown, collapsed reasoning, tool cards (knownTools.tsx-parity presentation, status chip, read-only permission row w/ pending banner, per-tool bodies: terminal, before/after edits, write content, unified-diff detection -> DiffTextView, checklists, read-only questions, pretty-JSON fallback, children rail), tool groups, centered event rows (presentation.ts port), cli output, generated images (authed bytes + in-memory cache + full-screen viewer), codex review verdict card. - Navigation/links: session list now pushes ChatView; app-level \.hapiOpenURL handler (https/http -> SFSafariViewController, custom schemes confirm first, hapi-file:// -> M4 placeholder) + theme injection. - Tests: ChatPipelineTests drive the runner with fixture-derived windows (stable ids, memo-stable recomputes, queued filter, group-id stability across an older-page arrival). - README: M2f app-layer description. |