π¦ β‘ ΠΠ΅Π»Π°Π΅ΠΌ Π½Π°ΡΠΈΠ²Π½ΠΎΠ΅ ΠΌΠΎΠ±ΠΈΠ»ΡΠ½ΠΎΠ΅ ΠΏΡΠΈΠ»ΠΎΠΆΠ΅Π½ΠΈΠ΅ Ρ ΠΠ ΠΈ Π±ΡΠΊΠ΅Π½Π΄ΠΎΠΌ
Π ΡΡΠΎΠΉ ΡΡΠ°ΡΡΠ΅ ΠΌΡ ΡΠ°ΡΡΠΌΠΎΡΡΠΈΠΌ ΠΏΡΠΈΠΌΠ΅Ρ ΡΠΎΠ·Π΄Π°Π½ΠΈΡ Proof of Concept (PoC) ΠΌΠΎΠ±ΠΈΠ»ΡΠ½ΠΎΠ³ΠΎ ΠΏΡΠΈΠ»ΠΎΠΆΠ΅Π½ΠΈΡ, ΠΏΠΎΡΡΡΠΎΠ΅Π½Π½ΠΎΠ³ΠΎ Ρ ΠΏΠΎΠΌΠΎΡΡΡ ΡΡΠ΅ΠΉΠΌΠ²ΠΎΡΠΊΠ° SwiftUI ΠΈ Π±ΡΠΊΠ΅Π½Π΄Π° Ρ ΠΈΡΠΏΠΎΠ»ΡΠ·ΠΎΠ²Π°Π½ΠΈΠ΅ΠΌ FastAPI. ΠΠΎΠΏΠΎΠ»Π½ΠΈΡΠ΅Π»ΡΠ½ΠΎ Ρ ΠΏΡΠΎΠ΄Π΅ΠΌΠΎΠ½ΡΡΡΠΈΡΡΡ ΡΡΡΠ΅ΠΊΡΠΈΠ²Π½ΡΠ΅ Π°ΡΡ ΠΈΡΠ΅ΠΊΡΡΡΠ½ΡΠ΅ ΠΏΠ°ΡΡΠ΅ΡΠ½Ρ Π΄Π»Ρ SwiftUI-ΠΏΡΠΈΠ»ΠΎΠΆΠ΅Π½ΠΈΠΉ, Π² ΡΠ°ΡΡΠ½ΠΎΡΡΠΈ MVVMP Π² ΡΠΎΡΠ΅ΡΠ°Π½ΠΈΠΈ Ρ ΠΏΡΠΈΠ½ΡΠΈΠΏΠ°ΠΌΠΈ SOLID, Dependency Injection (DI), KISS ΠΈ DRY. ΠΠ»Ρ android ΠΊΠΎΠ΄ ΠΌΠΎΠΆΠ½ΠΎ Π»Π΅Π³ΠΊΠΎ ΠΏΠ΅ΡΠ΅Π²Π΅ΡΡΠΈ Π½Π° Kotlin Ρ ΠΏΠΎΠΌΠΎΡΡΡ Jetpack Compose Framework.
Π ΡΡΠΎΠΉ ΡΡΠ°ΡΡΠ΅ ΠΌΡ ΡΠ°ΡΡΠΌΠΎΡΡΠΈΠΌ Π²ΡΠ΅ ΡΠΎΠ½ΠΊΠΎΡΡΠΈ ΡΠΎΠ·Π΄Π°Π½ΠΈΡ Proof of Concept (PoC) ΠΌΠΎΠ±ΠΈΠ»ΡΠ½ΠΎΠ³ΠΎ ΠΏΡΠΈΠ»ΠΎΠΆΠ΅Π½ΠΈΡ, ΠΏΠΎΡΡΡΠΎΠ΅Π½Π½ΠΎΠ³ΠΎ Ρ ΠΏΠΎΠΌΠΎΡΡΡ ΡΡΠ΅ΠΉΠΌΠ²ΠΎΡΠΊΠ° SwiftUI ΠΈ Π±ΡΠΊΠ΅Π½Π΄Π° Ρ ΠΈΡΠΏΠΎΠ»ΡΠ·ΠΎΠ²Π°Π½ΠΈΠ΅ΠΌ FastAPI. ΠΠΎΠΏΠΎΠ»Π½ΠΈΡΠ΅Π»ΡΠ½ΠΎ Ρ ΠΏΡΠΎΠ΄Π΅ΠΌΠΎΠ½ΡΡΡΠΈΡΡΡ ΡΡΡΠ΅ΠΊΡΠΈΠ²Π½ΡΠ΅ Π°ΡΡ ΠΈΡΠ΅ΠΊΡΡΡΠ½ΡΠ΅ ΠΏΠ°ΡΡΠ΅ΡΠ½Ρ Π΄Π»Ρ SwiftUI-ΠΏΡΠΈΠ»ΠΎΠΆΠ΅Π½ΠΈΠΉ, Π² ΡΠ°ΡΡΠ½ΠΎΡΡΠΈ MVVMP Π² ΡΠΎΡΠ΅ΡΠ°Π½ΠΈΠΈ Ρ ΠΏΡΠΈΠ½ΡΠΈΠΏΠ°ΠΌΠΈ SOLID ΠΈ Dependency Injection (DI). ΠΠ»Ρ android ΠΊΠΎΠ΄ ΠΌΠΎΠΆΠ½ΠΎ Π»Π΅Π³ΠΊΠΎ ΠΏΠ΅ΡΠ΅Π²Π΅ΡΡΠΈ Π½Π° Kotlin Ρ ΠΏΠΎΠΌΠΎΡΡΡ Jetpack Compose Framework.
ΠΠ°ΡΠ΅ΠΌ Π½Π°ΠΌ Π½ΡΠΆΠ΅Π½ Π±ΡΠΊΠ΅Π½Π΄
ΠΡΠΎ-ΡΠΎ ΠΌΠΎΠΆΠ΅Ρ ΡΠΊΠ°Π·Π°ΡΡ, ΡΡΠΎ ΠΌΠΎΠΆΠ½ΠΎ ΠΏΡΠΎΡΡΠΎ Π·Π°ΠΏΠΈΡ Π½ΡΡΡ Π²ΡΡ Π»ΠΎΠ³ΠΈΠΊΡ Π² ΠΏΡΠΈΠ»ΠΎΠΆΠ΅Π½ΠΈΠ΅, Π½Π°ΠΏΡΡΠΌΡΡ ΠΎΡΠΏΡΠ°Π²Π»ΡΡΡ Π·Π°ΠΏΡΠΎΡΡ Π² chatgpt ΠΈ ΡΠ΄Π΅Π»Π°ΡΡ ΠΏΡΠΈΠ»ΠΎΠΆΠ΅Π½ΠΈΠ΅ Π±Π΅Π· Π±ΡΠΊΠ΅Π½Π΄Π°. Π Ρ ΡΠΎΠ³Π»Π°ΡΠ΅Π½, ΡΡΠΎ Π΄Π΅ΠΉΡΡΠ²ΠΈΡΠ΅Π»ΡΠ½ΠΎ Π²ΠΎΠ·ΠΌΠΎΠΆΠ½ΠΎ, Π½ΠΎ Π±ΡΠΊΠ΅Π½Π΄ Π΄Π°Π΅Ρ Π½Π΅ΡΠΊΠΎΠ»ΡΠΊΠΎ Π²Π°ΠΆΠ½ΡΡ ΠΏΡΠ΅ΠΈΠΌΡΡΠ΅ΡΡΠ².
ΠΡΠΊΠ΅Π½Π΄ ΡΠ»ΡΠΆΠΈΡ ΠΎΡΠ½ΠΎΠ²ΠΎΠΉ Π΄Π»Ρ Π»ΡΠ±ΠΎΠ³ΠΎ ΡΠ»ΠΎΠΆΠ½ΠΎΠ³ΠΎ ΠΏΡΠΈΠ»ΠΎΠΆΠ΅Π½ΠΈΡ, ΠΎΡΠΎΠ±Π΅Π½Π½ΠΎ Π΄Π»Ρ ΡΠ΅Ρ , ΠΊΠΎΡΠΎΡΡΠ΅ ΡΡΠ΅Π±ΡΡΡ Π±Π΅Π·ΠΎΠΏΠ°ΡΠ½ΠΎΠ³ΠΎ ΡΠΏΡΠ°Π²Π»Π΅Π½ΠΈΡ Π΄Π°Π½Π½ΡΠΌΠΈ, ΠΎΠ±ΡΠ°Π±ΠΎΡΠΊΠΈ Π±ΠΈΠ·Π½Π΅Ρ-Π»ΠΎΠ³ΠΈΠΊΠΈ ΠΈ ΠΈΠ½ΡΠ΅Π³ΡΠ°ΡΠΈΠΈ ΡΠ΅ΡΠ²ΠΈΡΠΎΠ². ΠΠΎΡ ΠΏΠΎΡΠ΅ΠΌΡ Π½Π°Π΄Π΅ΠΆΠ½ΡΠΉ Π±ΡΠΊΡΠ½Π΄ ΠΈΠΌΠ΅Π΅Ρ ΡΠ΅ΡΠ°ΡΡΠ΅Π΅ Π·Π½Π°ΡΠ΅Π½ΠΈΠ΅:
- ΠΠ΅Π·ΠΎΠΏΠ°ΡΠ½ΠΎΡΡΡ: ΠΡΠΊΡΠ½Π΄ ΠΏΠΎΠΌΠΎΠ³Π°Π΅Ρ Π·Π°ΡΠΈΡΠΈΡΡ ΠΊΠΎΠ½ΡΠΈΠ΄Π΅Π½ΡΠΈΠ°Π»ΡΠ½ΡΠ΅ Π΄Π°Π½Π½ΡΠ΅ ΠΈ ΡΠΎΠΊΠ΅Π½Ρ Π°ΡΡΠ΅Π½ΡΠΈΡΠΈΠΊΠ°ΡΠΈΠΈ ΠΏΠΎΠ»ΡΠ·ΠΎΠ²Π°ΡΠ΅Π»Π΅ΠΉ ΠΎΡ Π°ΡΠ°ΠΊ ΡΠΈΠΏΠ° MITM (Man-in-the-Middle). ΠΠ½ Π²ΡΡΡΡΠΏΠ°Π΅Ρ Π² ΠΊΠ°ΡΠ΅ΡΡΠ²Π΅ Π·Π°ΡΠΈΡΠ΅Π½Π½ΠΎΠ³ΠΎ ΡΠ»ΡΠ·Π° ΠΌΠ΅ΠΆΠ΄Ρ ΠΏΠΎΠ»ΡΠ·ΠΎΠ²Π°ΡΠ΅Π»ΡΡΠΊΠΈΠΌ ΡΡΡΡΠΎΠΉΡΡΠ²ΠΎΠΌ ΠΈ Π±Π°Π·ΠΎΠΉ Π΄Π°Π½Π½ΡΡ ΠΈΠ»ΠΈ Π²Π½Π΅ΡΠ½ΠΈΠΌΠΈ ΡΠ»ΡΠΆΠ±Π°ΠΌΠΈ, ΠΎΠ±Π΅ΡΠΏΠ΅ΡΠΈΠ²Π°Ρ ΡΠΈΡΡΠΎΠ²Π°Π½ΠΈΠ΅ ΠΈ Π°ΡΡΠ΅Π½ΡΠΈΡΠΈΠΊΠ°ΡΠΈΡ Π²ΡΠ΅Ρ Π΄Π°Π½Π½ΡΡ .
- ΠΠΎΠ½ΡΡΠΎΠ»Ρ Π½Π°Π΄ ΠΈΡΠΏΠΎΠ»ΡΠ·ΠΎΠ²Π°Π½ΠΈΠ΅ΠΌ ΡΠ΅ΡΠ²ΠΈΡΠΎΠ²: Π£ΠΏΡΠ°Π²Π»ΡΡ API ΠΈ Π²Π·Π°ΠΈΠΌΠΎΠ΄Π΅ΠΉΡΡΠ²ΠΈΠ΅ΠΌ Ρ ΠΏΠΎΠ»ΡΠ·ΠΎΠ²Π°ΡΠ΅Π»ΡΠΌΠΈ ΡΠ΅ΡΠ΅Π· Π±ΡΠΊΡΠ½Π΄, Π²Ρ ΠΌΠΎΠΆΠ΅ΡΠ΅ ΠΎΡΡΠ»Π΅ΠΆΠΈΠ²Π°ΡΡ ΠΈ ΠΊΠΎΠ½ΡΡΠΎΠ»ΠΈΡΠΎΠ²Π°ΡΡ ΠΈΡΠΏΠΎΠ»ΡΠ·ΠΎΠ²Π°Π½ΠΈΠ΅ ΠΏΡΠΈΠ»ΠΎΠΆΠ΅Π½ΠΈΡ. ΠΡΠΎ Π²ΠΊΠ»ΡΡΠ°Π΅Ρ Π² ΡΠ΅Π±Ρ Π΄ΡΠΎΡΡΠ΅Π»ΠΈΡΠΎΠ²Π°Π½ΠΈΠ΅ Π΄Π»Ρ ΡΠΏΡΠ°Π²Π»Π΅Π½ΠΈΡ Π½Π°Π³ΡΡΠ·ΠΊΠΎΠΉ, ΠΏΡΠ΅Π΄ΠΎΡΠ²ΡΠ°ΡΠ΅Π½ΠΈΠ΅ Π·Π»ΠΎΡΠΏΠΎΡΡΠ΅Π±Π»Π΅Π½ΠΈΠΉ ΠΈ ΠΎΠ±Π΅ΡΠΏΠ΅ΡΠ΅Π½ΠΈΠ΅ ΡΡΡΠ΅ΠΊΡΠΈΠ²Π½ΠΎΠ³ΠΎ ΠΈΡΠΏΠΎΠ»ΡΠ·ΠΎΠ²Π°Π½ΠΈΡ ΡΠ΅ΡΡΡΡΠΎΠ².
- ΠΠ½ΡΠ΅Π³ΡΠ°ΡΠΈΡ Ρ Π±Π°Π·ΠΎΠΉ Π΄Π°Π½Π½ΡΡ : ΠΡΠΊΡΠ½Π΄ ΠΎΠ±Π΅ΡΠΏΠ΅ΡΠΈΠ²Π°Π΅Ρ Π±Π΅ΡΡΠΎΠ²Π½ΡΡ ΠΈΠ½ΡΠ΅Π³ΡΠ°ΡΠΈΡ Ρ Π±Π°Π·Π°ΠΌΠΈ Π΄Π°Π½Π½ΡΡ , ΠΏΠΎΠ·Π²ΠΎΠ»ΡΡ Π΄ΠΈΠ½Π°ΠΌΠΈΡΠ΅ΡΠΊΠΈ Ρ ΡΠ°Π½ΠΈΡΡ, ΠΈΠ·Π²Π»Π΅ΠΊΠ°ΡΡ ΠΈ ΠΎΠ±Π½ΠΎΠ²Π»ΡΡΡ Π΄Π°Π½Π½ΡΠ΅ Π² ΡΠ΅ΠΆΠΈΠΌΠ΅ ΡΠ΅Π°Π»ΡΠ½ΠΎΠ³ΠΎ Π²ΡΠ΅ΠΌΠ΅Π½ΠΈ. ΠΡΠΎ Π²Π°ΠΆΠ½ΠΎ Π΄Π»Ρ ΠΏΡΠΈΠ»ΠΎΠΆΠ΅Π½ΠΈΠΉ, ΠΊΠΎΡΠΎΡΡΠ΅ ΡΡΠ΅Π±ΡΡΡ ΡΡΠ΅ΡΠ½ΡΡ Π·Π°ΠΏΠΈΡΠ΅ΠΉ ΠΏΠΎΠ»ΡΠ·ΠΎΠ²Π°ΡΠ΅Π»Π΅ΠΉ, Ρ ΡΠ°Π½ΡΡ ΠΈΡ ΠΏΡΠ΅Π΄ΠΏΠΎΡΡΠ΅Π½ΠΈΡ ΠΈΠ»ΠΈ Π½ΡΠΆΠ΄Π°ΡΡΡΡ Π² Π±ΡΡΡΡΠΎΠΌ ΠΈ Π±Π΅Π·ΠΎΠΏΠ°ΡΠ½ΠΎΠΌ ΠΏΠΎΠ»ΡΡΠ΅Π½ΠΈΠΈ Π±ΠΎΠ»ΡΡΠΈΡ ΠΎΠ±ΡΠ΅ΠΌΠΎΠ² Π΄Π°Π½Π½ΡΡ .
- ΠΠΎΠ΄Π΅Π»ΠΈ ΠΏΠΎΠ΄ΠΏΠΈΡΠΊΠΈ ΠΈ Freemium: Π Π΅Π°Π»ΠΈΠ·Π°ΡΠΈΡ ΡΡΠ»ΡΠ³ ΠΏΠΎ ΠΏΠΎΠ΄ΠΏΠΈΡΠΊΠ΅ ΠΈΠ»ΠΈ ΠΌΠΎΠ΄Π΅Π»ΠΈ freemium ΡΡΠ΅Π±ΡΠ΅Ρ Π½Π°Π»ΠΈΡΠΈΡ Π±ΡΠΊΠ΅Π½Π΄Π° Π΄Π»Ρ Π²ΡΡΡΠ°Π²Π»Π΅Π½ΠΈΡ ΡΡΠ΅ΡΠΎΠ², ΠΎΡΡΠ»Π΅ΠΆΠΈΠ²Π°Π½ΠΈΡ ΠΈΡΠΏΠΎΠ»ΡΠ·ΠΎΠ²Π°Π½ΠΈΡ ΠΈ ΡΠΏΡΠ°Π²Π»Π΅Π½ΠΈΡ ΡΡΠΎΠ²Π½ΡΠΌΠΈ ΠΏΠΎΠ»ΡΠ·ΠΎΠ²Π°ΡΠ΅Π»Π΅ΠΉ. ΠΡΠΊΡΠ½Π΄ ΠΌΠΎΠΆΠ΅Ρ Π±Π΅Π·ΠΎΠΏΠ°ΡΠ½ΠΎ ΠΎΠ±ΡΠ°Π±Π°ΡΡΠ²Π°ΡΡ ΠΏΠ»Π°ΡΠ΅ΠΆΠΈ ΠΈ ΠΏΠΎΠ΄ΠΏΠΈΡΠΊΠΈ, ΠΎΠ±Π΅ΡΠΏΠ΅ΡΠΈΠ²Π°Ρ Π±Π΅ΡΠΏΠ΅ΡΠ΅Π±ΠΎΠΉΠ½ΡΡ ΡΠ°Π±ΠΎΡΡ ΠΏΠΎΠ»ΡΠ·ΠΎΠ²Π°ΡΠ΅Π»Π΅ΠΉ ΠΈ ΡΠΎΠ±Π»ΡΠ΄Π°Ρ ΡΡΠ΅Π±ΠΎΠ²Π°Π½ΠΈΡ ΠΏΠΎ Π·Π°ΡΠΈΡΠ΅ Π΄Π°Π½Π½ΡΡ .
- ΠΠ°ΡΡΡΠ°Π±ΠΈΡΡΠ΅ΠΌΠΎΡΡΡ ΠΈ ΠΎΠ±ΡΠ»ΡΠΆΠΈΠ²Π°Π½ΠΈΠ΅: ΠΡΠΊΡΠ½Π΄ ΠΏΠΎΠ·Π²ΠΎΠ»ΡΠ΅Ρ Π±ΠΎΠ»Π΅Π΅ ΡΡΡΠ΅ΠΊΡΠΈΠ²Π½ΠΎ ΠΌΠ°ΡΡΡΠ°Π±ΠΈΡΠΎΠ²Π°ΡΡ ΠΏΡΠΈΠ»ΠΎΠΆΠ΅Π½ΠΈΠ΅. ΠΠΎΠ³ΠΈΠΊΡ Π½Π° ΡΡΠΎΡΠΎΠ½Π΅ ΡΠ΅ΡΠ²Π΅ΡΠ° ΠΌΠΎΠΆΠ½ΠΎ ΠΎΠ±Π½ΠΎΠ²Π»ΡΡΡ Π±Π΅Π· Π½Π΅ΠΎΠ±Ρ ΠΎΠ΄ΠΈΠΌΠΎΡΡΠΈ ΠΏΠ΅ΡΠ΅Π΄Π°Π²Π°ΡΡ ΠΎΠ±Π½ΠΎΠ²Π»Π΅Π½ΠΈΡ ΠΊΠ»ΠΈΠ΅Π½ΡΡ, ΡΡΠΎ ΡΠΏΡΠΎΡΠ°Π΅Ρ ΠΎΠ±ΡΠ»ΡΠΆΠΈΠ²Π°Π½ΠΈΠ΅ ΠΈ ΡΡΠΊΠΎΡΡΠ΅Ρ Π²Π½Π΅Π΄ΡΠ΅Π½ΠΈΠ΅ Π½ΠΎΠ²ΡΡ ΡΡΠ½ΠΊΡΠΈΠΉ.
ΠΠΎ ΡΡΡΠΈ, Π±ΡΠΊΠ΅Π½Π΄ β ΡΡΠΎ Π½Π΅ ΡΠΎΠ»ΡΠΊΠΎ ΡΡΠ½ΠΊΡΠΈΠΎΠ½Π°Π»ΡΠ½ΠΎΡΡΡ, Π½ΠΎ ΠΈ ΡΠΎΠ·Π΄Π°Π½ΠΈΠ΅ Π±Π΅Π·ΠΎΠΏΠ°ΡΠ½ΠΎΠΉ, ΠΌΠ°ΡΡΡΠ°Π±ΠΈΡΡΠ΅ΠΌΠΎΠΉ ΠΈ ΡΡΡΠΎΠΉΡΠΈΠ²ΠΎΠΉ ΡΡΠ΅Π΄Ρ Π΄Π»Ρ ΠΏΡΠΎΡΠ²Π΅ΡΠ°Π½ΠΈΡ Π²Π°ΡΠ΅Π³ΠΎ ΠΏΡΠΈΠ»ΠΎΠΆΠ΅Π½ΠΈΡ.
ΠΠ±ΡΡΡΠ½Π΅Π½ΠΈΠ΅ ΡΠ΅Ρ Π½ΠΈΡΠ΅ΡΠΊΠΎΠ³ΠΎ ΡΡΠ΅ΠΊΠ°
- SwiftUI: ΠΡΡΡΠΈΠΉ Π²Π°ΡΠΈΠ°Π½Ρ Π΄Π»Ρ Π½Π°ΡΠΈΠ²Π½ΡΡ ΠΏΡΠΈΠ»ΠΎΠΆΠ΅Π½ΠΈΠΉ Π΄Π»Ρ iOS ΠΏΠΎΡΠ»Π΅ Π²ΡΡ ΠΎΠ΄Π° UIKit. ΠΠ½ Π΄Π΅ΠΊΠ»Π°ΡΠ°ΡΠΈΠ²Π΅Π½ ΠΈ ΡΠΏΠΎΡΡΠ΄ΠΎΡΠ΅Π½, Π° XCode ΡΠ²Π»ΡΠ΅ΡΡΡ Π½Π΅Π·Π°ΠΌΠ΅Π½ΠΈΠΌΡΠΌ ΡΠ΅Π΄Π°ΠΊΡΠΎΡΠΎΠΌ Π±Π»Π°Π³ΠΎΠ΄Π°ΡΡ ΡΠΏΠ». ΠΠ»Ρ android ΠΊΠΎΠ΄ ΠΌΠΎΠΆΠ½ΠΎ Π»Π΅Π³ΠΊΠΎ ΠΏΠ΅ΡΠ΅Π²Π΅ΡΡΠΈ Π½Π° Kotlin Ρ ΠΏΠΎΠΌΠΎΡΡΡ Jetpack Compose.
- FastAPI: ΠΡΠ±ΡΠ°Π½ Π΄Π»Ρ Π±ΡΠΊΠ΅Π½Π΄Π° Π·Π° Π΅Π³ΠΎ ΡΠΊΠΎΡΠΎΡΡΡ, ΠΌΠΈΠ½ΠΈΠΌΠ°Π»ΡΠ½ΠΎΠ΅ ΠΊΠΎΠ»ΠΈΡΠ΅ΡΡΠ²ΠΎ ΡΠ°Π±Π»ΠΎΠ½ΠΎΠ² ΠΈ Π΄Π΅ΠΊΠ»Π°ΡΠ°ΡΠΈΠ²Π½ΠΎΡΡΡ, ΡΠ΅Π΄Π°ΠΊΡΠΈΡΡΠ΅ΡΡΡ Ρ ΠΏΠΎΠΌΠΎΡΡΡ ΠΏΡΠ΅Π²ΠΎΡΡ ΠΎΠ΄Π½ΠΎΠ³ΠΎ Zed.dev.
- ChatGPT API: ΠΡΠΏΠΎΠ»ΡΠ·ΡΠ΅ΡΡΡ Π² ΠΊΠ°ΡΠ΅ΡΡΠ²Π΅ Π±ΠΎΠ»ΡΡΠΎΠΉ ΡΠ·ΡΠΊΠΎΠ²ΠΎΠΉ ΠΌΠΎΠ΄Π΅Π»ΠΈ (LLM); Π²ΡΠ±ΠΎΡ ΠΌΠΎΠΆΠ΅Ρ ΠΌΠ΅Π½ΡΡΡΡΡ Π² Π·Π°Π²ΠΈΡΠΈΠΌΠΎΡΡΠΈ ΠΎΡ Π½Π΅ΠΎΠ±Ρ ΠΎΠ΄ΠΈΠΌΠΎΡΡΠΈ ΠΊΠ°ΡΡΠΎΠΌΠΈΠ·Π°ΡΠΈΠΈ.
- Ngrok: Π Π΅Π°Π»ΠΈΠ·ΡΠ΅Ρ ΡΡΠ½Π½Π΅Π»ΠΈΡΠΎΠ²Π°Π½ΠΈΠ΅ Ρ ΠΏΠΎΠΌΠΎΡΡΡ ΠΏΡΠΎΡΡΠΎΠΉ ΠΊΠΎΠΌΠ°Π½Π΄Ρ CLI Π΄Π»Ρ Π²ΡΡ ΠΎΠ΄Π° Π»ΠΎΠΊΠ°Π»ΡΠ½ΠΎΠ³ΠΎ ΡΠ΅ΡΠ²Π΅ΡΠ° Π² ΠΈΠ½ΡΠ΅ΡΠ½Π΅Ρ.
Π‘ΠΎΠ·Π΄Π°Π½ΠΈΠ΅ ΠΏΡΠΈΠ»ΠΎΠΆΠ΅Π½ΠΈΡ Π΄Π»Ρ iOS
Π’Π΅ΠΎΡΠΈΡ: ΠΡΡ ΠΈΡΠ΅ΠΊΡΡΡΠ½ΡΠ΅ ΠΏΠ°ΡΡΠ΅ΡΠ½Ρ
1. MVVMP (Model View ViewModel Presenter):
- Model: ΠΡΠ΅Π΄ΡΡΠ°Π²Π»ΡΠ΅Ρ ΡΠΎΠ±ΠΎΠΉ ΡΡΡΡΠΊΡΡΡΡ Π΄Π°Π½Π½ΡΡ , ΠΈΡΠΏΠΎΠ»ΡΠ·ΡΠ΅ΠΌΡΠ΅ Π² ΠΏΡΠΈΠ»ΠΎΠΆΠ΅Π½ΠΈΠΈ, ΡΠ°ΠΊΠΈΠ΅ ΠΊΠ°ΠΊ Question, Answer, Questionary ΠΈ FilledQuestionary. ΠΡΠΈ ΠΌΠΎΠ΄Π΅Π»ΠΈ ΠΏΡΠΎΡΡΡ ΠΈ ΡΠΎΠ΄Π΅ΡΠΆΠ°Ρ ΡΠΎΠ»ΡΠΊΠΎ Π΄Π°Π½Π½ΡΠ΅, ΡΠ»Π΅Π΄ΡΡ ΠΏΡΠΈΠ½ΡΠΈΠΏΡ KISS.
- View: ΠΡΠ²Π΅ΡΠ°ΡΡ ΡΠΎΠ»ΡΠΊΠΎ Π·Π° ΠΏΡΠ΅Π΄ΡΡΠ°Π²Π»Π΅Π½ΠΈΠ΅ ΠΏΠΎΠ»ΡΠ·ΠΎΠ²Π°ΡΠ΅Π»ΡΡΠΊΠΎΠ³ΠΎ ΠΈΠ½ΡΠ΅ΡΡΠ΅ΠΉΡΠ° ΠΈ Π΄Π΅Π»Π΅Π³ΠΈΡΡΡΡ Π²ΡΠ΅ Π΄Π°Π½Π½ΡΠ΅ ΠΈ Π»ΠΎΠ³ΠΈΠΊΡ ΠΏΡΠ΅Π·Π΅Π½ΡΠ΅ΡΠ°ΠΌ. ΠΠ½ΠΈ Π½Π΅ ΡΠΎΠ΄Π΅ΡΠΆΠ°Ρ Π½ΠΈΠΊΠ°ΠΊΠΎΠΉ Π±ΠΈΠ·Π½Π΅Ρ-Π»ΠΎΠ³ΠΈΠΊΠΈ ΠΈ ΡΠΏΡΠΎΠ΅ΠΊΡΠΈΡΠΎΠ²Π°Π½Ρ ΡΠ°ΠΊ, ΡΡΠΎΠ±Ρ Π±ΡΡΡ ΠΏΡΠΎΡΡΡΠΌΠΈ ΠΈ ΡΠΎΡΡΠ΅Π΄ΠΎΡΠΎΡΠ΅Π½Π½ΡΠΌΠΈ Π½Π° ΡΠ΅Π½Π΄Π΅ΡΠ΅ UI.
- ViewModel: Π SwiftUI ViewModel ΠΏΡΠ΅Π΄ΡΡΠ°Π²Π»Π΅Π½Π° ΠΎΠ±ΡΠ΅ΠΊΡΠΎΠΌ ObservableObject, ΠΊΠΎΡΠΎΡΡΠΉ ΡΠ»ΡΠΆΠΈΡ ΠΌΠΎΠ΄Π΅Π»ΡΡ Π½Π°Π±Π»ΡΠ΄Π΅Π½ΠΈΡ Π·Π° ΠΈΠ·ΠΌΠ΅Π½ΡΠ΅ΠΌΡΠΌΠΈ Π΄Π°Π½Π½ΡΠΌΠΈ. ΠΠ΄Π΅ΡΡ Π½Π΅Ρ ΠΌΠ΅ΡΠΎΠ΄ΠΎΠ² ΠΈ Π»ΠΎΠ³ΠΈΠΊΠΈ.
- Presenter: Presenter ΡΠΏΡΠ°Π²Π»ΡΠ΅Ρ Π²ΡΠ΅ΠΉ Π»ΠΎΠ³ΠΈΠΊΠΎΠΉ, ΡΠ²ΡΠ·Π°Π½Π½ΠΎΠΉ Ρ ΠΌΠΎΠ΄ΡΠ»Π΅ΠΌ (ΡΠΊΡΠ°Π½ΠΎΠΌ ΠΈΠ»ΠΈ ΠΏΡΠ΅Π΄ΡΡΠ°Π²Π»Π΅Π½ΠΈΠ΅ΠΌ), Π½ΠΎ Π½Π΅ Π±ΠΈΠ·Π½Π΅Ρ-Π»ΠΎΠ³ΠΈΠΊΠΎΠΉ. ΠΠ½ Π²Π·Π°ΠΈΠΌΠΎΠ΄Π΅ΠΉΡΡΠ²ΡΠ΅Ρ Ρ Π΄ΠΎΠΌΠ΅Π½Π½ΡΠΌ ΡΠ»ΠΎΠ΅ΠΌ Π΄Π»Ρ Π²ΡΠΏΠΎΠ»Π½Π΅Π½ΠΈΡ ΠΎΠΏΠ΅ΡΠ°ΡΠΈΠΉ Π±ΠΈΠ·Π½Π΅Ρ-Π»ΠΎΠ³ΠΈΠΊΠΈ, ΡΠ°ΠΊΠΈΡ ΠΊΠ°ΠΊ Π²Π·Π°ΠΈΠΌΠΎΠ΄Π΅ΠΉΡΡΠ²ΠΈΠ΅ Ρ API ΠΈΠ»ΠΈ ΡΠΏΡΠ°Π²Π»Π΅Π½ΠΈΠ΅ ΡΠΎΡ ΡΠ°Π½Π΅Π½ΠΈΠ΅ΠΌ Π΄Π°Π½Π½ΡΡ .
- Domain Layer: ΠΡΠΎΡ ΡΠ»ΠΎΠΉ ΡΠΎΠ΄Π΅ΡΠΆΠΈΡ Π±ΠΈΠ·Π½Π΅Ρ-Π»ΠΎΠ³ΠΈΠΊΡ ΠΏΡΠΈΠ»ΠΎΠΆΠ΅Π½ΠΈΡ ΠΈ Π²Π·Π°ΠΈΠΌΠΎΠ΄Π΅ΠΉΡΡΠ²ΡΠ΅Ρ Ρ Π²Π½Π΅ΡΠ½ΠΈΠΌΠΈ ΡΠ΅ΡΡΡΡΠ°ΠΌΠΈ, ΡΠ°ΠΊΠΈΠΌΠΈ ΠΊΠ°ΠΊ Π±Π°Π·Ρ Π΄Π°Π½Π½ΡΡ , API ΠΈΠ»ΠΈ Π΄ΡΡΠ³ΠΈΠ΅ ΡΠ΅ΡΠ²ΠΈΡΡ. ΠΠ½ ΡΠΎΡΡΠΎΠΈΡ ΠΈΠ· Π½Π΅ΡΠΊΠΎΠ»ΡΠΊΠΈΡ ΠΊΠΎΠΌΠΏΠΎΠ½Π΅Π½ΡΠΎΠ², ΡΠ°ΠΊΠΈΡ ΠΊΠ°ΠΊ ΡΠ΅ΡΠ²ΠΈΡΡ, ΠΏΡΠΎΠ²Π°ΠΉΠ΄Π΅ΡΡ, ΠΌΠ΅Π½Π΅Π΄ΠΆΠ΅ΡΡ, ΡΠ΅ΠΏΠΎΠ·ΠΈΡΠΎΡΠΈΠΈ, ΠΌΠ°ΠΏΠΏΠ΅ΡΡ, ΡΠ°Π±ΡΠΈΠΊΠΈ ΠΈ Ρ. Π΄.
- ΠΠ° ΡΠ°ΠΌΠΎΠΌ Π΄Π΅Π»Π΅, MP Π² MVVMP ΡΠ²Π»ΡΠ΅ΡΡΡ ΠΈΠ½ΠΈΡΠΈΠ°Π»Π°ΠΌΠΈ ΠΠ°ΡΠΊΠ° ΠΠ°ΡΠΊΠ΅ΡΠ°, Π° ΠΏΠΎΠ»Π½Π°Ρ ΡΠΎΡΠΌΠ° β Β«Model View ViewModel by Mark ParkerΒ».
2. ΠΡΠΈΠ½ΡΠΈΠΏΡ SOLID:
- ΠΡΠΈΠ½ΡΠΈΠΏ Π΅Π΄ΠΈΠ½ΠΎΠΉ ΠΎΡΠ²Π΅ΡΡΡΠ²Π΅Π½Π½ΠΎΡΡΠΈ: Π£ ΠΊΠ°ΠΆΠ΄ΠΎΠ³ΠΎ ΠΊΠ»Π°ΡΡΠ° Π΄ΠΎΠ»ΠΆΠ½Π° Π±ΡΡΡ ΡΠΎΠ»ΡΠΊΠΎ ΠΎΠ΄Π½Π° ΠΏΡΠΈΡΠΈΠ½Π° Π΄Π»Ρ ΠΈΠ·ΠΌΠ΅Π½Π΅Π½ΠΈΠΉ.
- ΠΡΠΈΠ½ΡΠΈΠΏ ΠΎΡΠΊΡΡΡΠΎΡΡΡ-Π·Π°ΠΊΡΡΡΠΎΡΡΡ: ΠΠΎΠΌΠΏΠΎΠ½Π΅Π½ΡΡ Π΄ΠΎΠ»ΠΆΠ½Ρ Π±ΡΡΡ ΠΎΡΠΊΡΡΡΡ Π΄Π»Ρ ΡΠ°ΡΡΠΈΡΠ΅Π½ΠΈΡ, Π½ΠΎ Π·Π°ΠΊΡΡΡΡ Π΄Π»Ρ ΠΌΠΎΠ΄ΠΈΡΠΈΠΊΠ°ΡΠΈΠΈ.
- ΠΡΠΈΠ½ΡΠΈΠΏ Π·Π°ΠΌΠ΅ΡΠ΅Π½ΠΈΡ ΠΠΈΡΠΊΠΎΠ²Π°: ΠΠ±ΡΠ΅ΠΊΡΡ ΡΡΠΏΠ΅ΡΠΊΠ»Π°ΡΡΠ° Π΄ΠΎΠ»ΠΆΠ½Ρ Π±ΡΡΡ Π·Π°ΠΌΠ΅Π½ΡΠ΅ΠΌΡ ΠΎΠ±ΡΠ΅ΠΊΡΠ°ΠΌΠΈ ΠΏΠΎΠ΄ΠΊΠ»Π°ΡΡΠΎΠ².
- ΠΡΠΈΠ½ΡΠΈΠΏ ΡΠ°Π·Π΄Π΅Π»Π΅Π½ΠΈΡ ΠΈΠ½ΡΠ΅ΡΡΠ΅ΠΉΡΠΎΠ²: ΠΠΈ ΠΎΠ΄ΠΈΠ½ ΠΊΠ»ΠΈΠ΅Π½Ρ Π½Π΅ Π΄ΠΎΠ»ΠΆΠ΅Π½ Π±ΡΡΡ Π²ΡΠ½ΡΠΆΠ΄Π΅Π½ Π·Π°Π²ΠΈΡΠ΅ΡΡ ΠΎΡ ΠΈΠ½ΡΠ΅ΡΡΠ΅ΠΉΡΠΎΠ², ΠΊΠΎΡΠΎΡΡΠ΅ ΠΎΠ½ Π½Π΅ ΠΈΡΠΏΠΎΠ»ΡΠ·ΡΠ΅Ρ.
- ΠΡΠΈΠ½ΡΠΈΠΏ ΠΈΠ½Π²Π΅ΡΡΠΈΠΈ Π·Π°Π²ΠΈΡΠΈΠΌΠΎΡΡΠ΅ΠΉ: ΠΠ°Π²ΠΈΡΠΈΠΌΠΎΡΡΡ ΠΎΡ Π°Π±ΡΡΡΠ°ΠΊΡΠΈΠΉ, Π° Π½Π΅ ΠΎΡ ΠΊΠΎΠ½ΠΊΡΠ΅ΡΠΈΠΊΠΈ, ΡΠ΅ΠΌΡ ΡΠΏΠΎΡΠΎΠ±ΡΡΠ²ΡΠ΅Ρ DI.
3. ΠΠ½ΡΠ΅ΠΊΡΠΈΡ Π·Π°Π²ΠΈΡΠΈΠΌΠΎΡΡΠ΅ΠΉ (DI):
- ΠΡΠΈ ΠΈΡΠΏΠΎΠ»ΡΠ·ΠΎΠ²Π°Π½ΠΈΠΈ ΠΏΠ°ΡΡΠ΅ΡΠ½Π° Dependency Injection Π·Π°Π²ΠΈΡΠΈΠΌΠΎΡΡΠΈ ΠΏΡΠ΅Π΄ΠΎΡΡΠ°Π²Π»ΡΡΡΡΡ ΠΈΠ·Π²Π½Π΅ ΠΊΠ»Π°ΡΡΠ°, Π° Π½Π΅ ΠΈΠ½ΡΡΠ°Π½ΡΠΈΡΡΡΡΡΡ Π²Π½ΡΡΡΠΈ Π½Π΅Π³ΠΎ. ΠΡΠΎ ΡΠΏΠΎΡΠΎΠ±ΡΡΠ²ΡΠ΅Ρ ΡΠ°Π·Π²ΡΠ·ΠΊΠ΅ ΠΈ ΠΏΠΎΠ·Π²ΠΎΠ»ΡΠ΅Ρ ΡΠΏΡΠΎΡΡΠΈΡΡ ΠΏΠΎΠ΄Π΄Π΅ΡΠΆΠΊΡ ΠΈ ΡΠ΅ΡΡΠΈΡΠΎΠ²Π°Π½ΠΈΠ΅ ΠΊΠΎΠ΄Π°.
Π Π°Π·ΡΠ°Π±ΠΎΡΠΊΠ° Π±ΡΠΊΠ΅Π½Π΄Π°
ΠΠΎΠ΄ Π±ΡΠΊΠ΅Π½Π΄Π° Π΄ΠΎΠ²ΠΎΠ»ΡΠ½ΠΎ ΠΏΡΠΎΡΡ. ΠΠ½Π΄ΠΏΠΎΠΈΠ½ΡΡ (main.py):
from typing import Callable import json from fastapi import FastAPI, Body, Request, Response from .models import (Question, FilledQuestionary, DoctorResponseAnswer, DoctorResponseQuestionary) from .user_card import UserCardSimple from .prompting import get_response @app.get("/onboarding", response_model = DoctorResponseQuestionary) def onboarding(): return DoctorResponseQuestionary(question=[Question(text=text) for text in UserCardSimple.__fields__.keys()]) @app.post("/doctor") def doctor(user_card: UserCardSimple, filled_questionary: FilledQuestionary, message: str = Body(...)): json_string = get_response(user_card, message, filled_questionary) loaded = json.loads(json_string.strip()) return loaded
"onboarding" ΠΏΡΠ΅Π΄ΠΎΡΡΠ°Π²Π»ΡΠ΅Ρ ΡΠΏΠΈΡΠΎΠΊ Π²ΠΎΠΏΡΠΎΡΠΎΠ² Π°Π½Π°ΠΌΠ½Π΅Π·Π°, ΠΊΠΎΡΠΎΡΡΠ΅ Π½Π΅ΠΎΠ±Ρ ΠΎΠ΄ΠΈΠΌΠΎ Π·Π°ΠΏΠΎΠ»Π½ΠΈΡΡ ΠΏΡΠΈ ΠΏΠ΅ΡΠ²ΠΎΠΌ Π·Π°ΠΏΡΡΠΊΠ΅ ΠΏΡΠΈΠ»ΠΎΠΆΠ΅Π½ΠΈΡ. ΠΡΠ²Π΅ΡΡ Π±ΡΠ΄ΡΡ ΡΠΎΡ ΡΠ°Π½Π΅Π½Ρ Π½Π° ΡΡΡΡΠΎΠΉΡΡΠ²Π΅ ΠΈ ΠΈΡΠΏΠΎΠ»ΡΠ·ΠΎΠ²Π°Π½Ρ Π΄Π»Ρ ΠΏΠ΅ΡΡΠΎΠ½Π°Π»ΠΈΠ·ΠΈΡΠΎΠ²Π°Π½Π½ΠΎΠΉ Π΄ΠΈΠ°Π³Π½ΠΎΡΡΠΈΠΊΠΈ Π² Π±ΡΠ΄ΡΡΠ΅ΠΌ. "doctor" β ΠΎΡΠ½ΠΎΠ²Π½ΠΎΠΉ ΡΠ½Π΄ΠΏΠΎΠΈΠ½Ρ: ΠΎΠ½ Π³Π΅Π½Π΅ΡΠΈΡΡΠ΅Ρ Π²ΠΎΠΏΡΠΎΡΡ Π½Π° ΠΎΡΠ½ΠΎΠ²Π΅ ΠΏΡΠ΅Π΄ΡΠ΄ΡΡΠΈΡ ΠΎΡΠ²Π΅ΡΠΎΠ² ΠΈ ΠΊΠ°ΡΡΡ ΠΏΠΎΠ»ΡΠ·ΠΎΠ²Π°ΡΠ΅Π»Ρ, Π»ΠΈΠ±ΠΎ Π²ΠΎΠ·Π²ΡΠ°ΡΠ°Π΅Ρ ΡΠ΅Π·ΡΠ»ΡΡΠ°Ρ Π΄ΠΈΠ°Π³Π½ΠΎΡΡΠΈΠΊΠΈ.
ΠΠΎΠ΄Π΅Π»ΠΈ:
from pydantic import BaseModel class Question(BaseModel): text: str class FilledQuestionary(BaseModel): filled_questions: dict[str, str] class DoctorResponseAnswer(BaseModel): text: str class DoctorResponseQuestionary(BaseModel): questions: list[str] class UserCardSimple(BaseModel): sex: str age: int weight: int height: int special_conditions: str
ΠΡΠΎΠΌΠΏΡΡ:
import os from openai import OpenAI from .models import FilledQuestionary api_key = os.environ.get("API_KEY") client = OpenAI(api_key=api_key) def get_response(user_card: str, message: str, filled_questionary: FilledQuestionary, max_tokens=200): format_question = """{"questions":[{"text":"first question"},{"text":"second question"}]}""" format_advice = """{"text":"Advice: Drink more water"}""" system_prompt = f""" You are a doctor that gives user an opportunity to swiftly check up health and diagnos an illness using anamnes and a short questionary. Your task is to ask short questions and give your opinion and advices. Your questions are accamulated in the filled questionary, which is empty in the first itteration. Strive to about 1-2 questions per iteration and up to 6 questions in total (can be less). Questions must be short, clear, shouldn't repeat, and should be relevant to the user's health condition, and should require easy answers. Ask questions only in the json format {format_question}. Number of answered questions: {len(filled_questionary.filled_questions)} If the Number of answered questions is more then 6, you should stop asking questions an`d provide an give your final opinion, an assumption or an advice in the json format {format_advice}. """ prompt = f"""request message: {message}; anamnesis: {user_card}; filled questionary: {filled_questionary};""" chat_completion = client.chat.completions.create( messages=[ { "role": "system", "content": f"{system_prompt}", }, { "role": "user", "content": f"{prompt}", }, ], model="gpt-3.5-turbo", max_tokens=max_tokens ) return chat_completion.choices[0].message.content
ΠΠΎΠ΄ΡΠ»Ρ ΠΏΡΠΎΠΌΠΏΡΠΎΠ² ΠΈΡΠΏΠΎΠ»ΡΠ·ΡΠ΅Ρ GPT-3.5 ΠΎΡ OpenAI Π΄Π»Ρ Π³Π΅Π½Π΅ΡΠ°ΡΠΈΠΈ ΠΎΡΠ²Π΅ΡΠΎΠ² Π½Π° ΠΎΡΠ½ΠΎΠ²Π΅ ΠΏΠΎΠ»ΡΠ·ΠΎΠ²Π°ΡΠ΅Π»ΡΡΠΊΠΎΠ³ΠΎ Π²Π²ΠΎΠ΄Π°, Π°Π½Π°ΠΌΠ½Π΅Π·Π° ΠΈ Π·Π°ΠΏΠΎΠ»Π½Π΅Π½Π½ΡΡ Π°Π½ΠΊΠ΅Ρ. ΠΠ½ Π²ΠΎΠ·Π²ΡΠ°ΡΠ°Π΅Ρ ΠΏΠΎΠ»ΡΠ·ΠΎΠ²Π°ΡΠ΅Π»Ρ ΡΠΎΠΎΡΠ²Π΅ΡΡΡΠ²ΡΡΡΠΈΠ΅ Π²ΠΎΠΏΡΠΎΡΡ ΠΈ ΡΠΎΠ²Π΅ΡΡ ΠΏΠΎ Π΄ΠΈΠ°Π³Π½ΠΎΡΡΠΈΠΊΠ΅ Π·Π΄ΠΎΡΠΎΠ²ΡΡ. ΠΠ°ΠΊ Π²ΠΈΠ΄ΠΈΡΠ΅, Π½ΠΈΡΠ΅Π³ΠΎ ΡΠ»ΠΎΠΆΠ½ΠΎΠ³ΠΎ Π·Π΄Π΅ΡΡ Π½Π΅Ρ. ΠΠΎΠ΄ ΡΠ»Π΅ΠΌΠ΅Π½ΡΠ°ΡΠ΅Π½, Π° ΠΏΡΠΎΠΌΠΏΡΡ β ΠΏΡΠΎΡΡΠΎ Π½Π°Π±ΠΎΡ ΡΠ΅ΡΠΊΠΈΡ ΠΈΠ½ΡΡΡΡΠΊΡΠΈΠΉ Π΄Π»Ρ LLM.
ΠΠ°ΡΡΡΠΎΠΉΡΠ΅ ΠΎΠΊΡΡΠΆΠ΅Π½ΠΈΠ΅ ΠΈ Π·Π°ΠΏΡΡΡΠΈΡΠ΅ ΡΠ΅ΡΠ²Π΅Ρ Ρ ΠΏΠΎΠΌΠΎΡΡΡ fastapi dev main.py
.
ΠΠΎΠ΄ΡΠΎΠ±Π½ΠΎΡΡΠΈ:
- fastapi.tiangolo.com/tutorial/first-steps
- pypi.org/project/openai/
ΠΡΠΊΡΡΡΠΈΠ΅ Π΄ΠΎΡΡΡΠΏΠ° ΠΊ Π»ΠΎΠΊΠ°Π»ΡΠ½ΠΎΠΌΡ Ρ ΠΎΡΡΡ ΡΠ΅ΡΠ΅Π· ΠΠ½ΡΠ΅ΡΠ½Π΅Ρ
- ΠΠ°ΡΠ΅Π³ΠΈΡΡΡΠΈΡΡΠΉΡΠ΅ΡΡ Π½Π° ΡΠ°ΠΉΡΠ΅ ngrok.com ΠΈ ΠΏΠΎΠ»ΡΡΠΈΡΠ΅ ΡΠΎΠΊΠ΅Π½ Π΄ΠΎΡΡΡΠΏΠ°.
- Π£ΡΡΠ°Π½ΠΎΠ²ΠΈΡΠ΅ ngrok Ρ ΡΠ°ΠΉΡΠ° ngrok.com/download.
- ΠΡΠΏΠΎΠ»Π½ΠΈΡΠ΅ ΠΊΠΎΠΌΠ°Π½Π΄Ρ
ngrok config add-authtoken <TOKEN>
. - ΠΠ°ΠΏΡΡΡΠΈΡΠ΅ Ρ ΠΏΠΎΠΌΠΎΡΡΡ ΠΊΠΎΠΌΠ°Π½Π΄Ρ
ngrok http http://localhost:8080
(ΠΏΡΠΈ Π½Π΅ΠΎΠ±Ρ ΠΎΠ΄ΠΈΠΌΠΎΡΡΠΈ ΠΈΠ·ΠΌΠ΅Π½ΠΈΡΠ΅ ΠΏΠΎΡΡ).
ΠΠΎΠ΄ΡΠΎΠ±Π½ΡΠ΅ ΠΈΠ½ΡΡΡΡΠΊΡΠΈΠΈ ΠΏΠΎ Π½Π°ΡΡΡΠΎΠΉΠΊΠ΅ ΠΌΠΎΠΆΠ½ΠΎ Π½Π°ΠΉΡΠΈ Π² Π΄ΠΎΠΊΡΠΌΠ΅Π½ΡΠ°ΡΠΈΠΈ ngrok.
ΠΠΎΠ΄ΠΈΠΌ ΠΏΡΠΈΠ»ΠΎΠΆΠ΅Π½ΠΈΠ΅
Π― Π½Π΅ Π±ΡΠ΄Ρ ΠΏΠΎΠΊΠ°Π·ΡΠ²Π°ΡΡ Π·Π΄Π΅ΡΡ Π²Π΅ΡΡ ΠΈΡΡ ΠΎΠ΄Π½ΡΠΉ ΠΊΠΎΠ΄, Π΄Π»Ρ ΡΡΠΎΠ³ΠΎ Π΅ΡΡΡ GitHub. ΠΠ°ΠΉΡΠΈ Π΅Π³ΠΎ ΠΌΠΎΠΆΠ½ΠΎ ΠΏΠΎ Π°Π΄ΡΠ΅ΡΡ: HouseMDAI iOS App. ΠΠΌΠ΅ΡΡΠΎ ΡΡΠΎΠ³ΠΎ Ρ ΠΎΡΡΠ°Π½ΠΎΠ²Π»ΡΡΡ ΡΠΎΠ»ΡΠΊΠΎ Π½Π° Π²Π°ΠΆΠ½ΡΡ (IMO) ΠΌΠΎΠΌΠ΅Π½ΡΠ°Ρ .
ΠΠ°ΡΠ½Π΅ΠΌ Ρ ΠΊΡΠ°ΡΠΊΠΎΠ³ΠΎ ΠΎΠΏΠΈΡΠ°Π½ΠΈΡ Π·Π°Π΄Π°ΡΠΈ: Π½Π°ΠΌ Π½ΡΠΆΠ½ΠΎ ΠΏΡΠΈΠ»ΠΎΠΆΠ΅Π½ΠΈΠ΅ Ρ ΡΠ΅ΠΊΡΡΠΎΠ²ΡΠΌ ΠΏΠΎΠ»Π΅ΠΌ Π½Π° Π³Π»Π°Π²Π½ΠΎΠΌ ΡΠΊΡΠ°Π½Π΅, Π²ΠΎΠ·ΠΌΠΎΠΆΠ½ΠΎΡΡΡΡ Π·Π°Π΄Π°Π²Π°ΡΡ Π½Π°Π±ΠΎΡ Π΄ΠΈΠ½Π°ΠΌΠΈΡΠ΅ΡΠΊΠΈΡ Π²ΠΎΠΏΡΠΎΡΠΎΠ² ΠΈ ΠΏΠΎΠΊΠ°Π·ΡΠ²Π°ΡΡ ΠΎΡΠ²Π΅Ρ. Π’Π°ΠΊΠΆΠ΅ Π½Π°ΠΌ Π½ΡΠΆΠ΅Π½ ΠΎΠ΄Π½ΠΎΡΠ°Π·ΠΎΠ²ΡΠΉ ΠΎΠ½Π±ΠΎΡΠ΄ΠΈΠ½Π³. ΠΡΠ°ΠΊ, ΠΏΡΠΈΡΡΡΠΏΠΈΠΌ ΠΊ ΠΊΠΎΠ΄Ρ.
ΠΠ΅ΡΠ²ΡΠΌ Π΄Π΅Π»ΠΎΠΌ Π½Π°ΠΌ Π½ΡΠΆΠ½Ρ ΠΌΠΎΠ΄Π΅Π»ΠΈ, ΠΈ ΠΎΠ½ΠΈ Π΄ΠΎΠ²ΠΎΠ»ΡΠ½ΠΎ ΠΏΡΠΎΡΡΡ (ΠΏΡΠΈΠ½ΡΠΈΠΏ KISS).
struct Question { var text: String } struct Answer { var text: String } struct Questionary { var questions: [Question] } struct FilledQuestionary { var filledQuestions: [String: String] }
Π’Π΅ΠΏΠ΅ΡΡ Π΄Π°Π²Π°ΠΉΡΠ΅ ΡΠ΄Π΅Π»Π°Π΅ΠΌ ΠΎΠ½Π±ΠΎΡΠ΄ΠΈΠ½Π³. ΠΡΠΎΠ΄ΠΎΠ»ΠΆΠ°Π΅ΠΌ ΡΠ»Π΅Π΄ΠΎΠ²Π°ΡΡ KISS ΠΈ SRP (Single Responsibility Principle), Π½ΠΈΠΊΠ°ΠΊΠΎΠΉ Π±ΠΈΠ·Π½Π΅Ρ-Π»ΠΎΠ³ΠΈΠΊΠΈ Π² ΠΏΡΠ΅Π΄ΡΡΠ°Π²Π»Π΅Π½ΠΈΡΡ
, ΡΠΎΠ»ΡΠΊΠΎ UI. Π Π΄Π°Π½Π½ΠΎΠΌ ΡΠ»ΡΡΠ°Π΅ β ΡΠΎΠ»ΡΠΊΠΎ ΡΠΏΠΈΡΠΎΠΊ Π²ΠΎΠΏΡΠΎΡΠΎΠ² Ρ ΠΏΡΠΎΠΊΡΡΡΠΊΠΎΠΉ. ΠΡΠ΅ Π΄Π°Π½Π½ΡΠ΅ ΠΈ Π»ΠΎΠ³ΠΈΠΊΠ° Π΄Π΅Π»Π΅Π³ΠΈΡΠΎΠ²Π°Π½Ρ ΠΏΡΠ΅Π·Π΅Π½ΡΠ΅ΡΡ. ΠΠ΄ΠΈΠ½ΡΡΠ²Π΅Π½Π½ΠΎΠ΅, ΡΡΠΎ Π·Π΄Π΅ΡΡ ΠΈΠ½ΡΠ΅ΡΠ΅ΡΠ½ΠΎ, ΡΡΠΎ Π½Π΅Π±ΠΎΠ»ΡΡΠΎΠΉ Π²ΡΠΏΠΎΠΌΠΎΠ³Π°ΡΠ΅Π»ΡΠ½ΡΠΉ ΠΌΠ΅ΡΠΎΠ΄ bindingForQuestion
, ΠΊΠΎΡΠΎΡΡΠΉ, Π²Π΅ΡΠΎΡΡΠ½ΠΎ, Π΄ΠΎΠ»ΠΆΠ΅Π½ Π±ΡΡΡ Π² ΠΏΡΠ΅Π·Π΅Π½ΡΠ΅ΡΠ΅, Π½ΠΎ ΡΠ΅ΠΉΡΠ°Ρ ΡΡΠΎ Π½Π΅ ΠΈΠΌΠ΅Π΅Ρ Π·Π½Π°ΡΠ΅Π½ΠΈΡ.
import SwiftUI struct OnboardingView: View { @StateObject var presenter: OnboardingPresenter var body: some View { ScrollView { Spacer() VStack { ForEach(presenter.questions.questions) { question in VStack { Text(question.text) TextField("", text: bindingForQuestion(question)) .formItem() } .padding() } }.padding() Button("Save", action: presenter.save) Spacer() } } private func bindingForQuestion(_ question: Question) -> Binding<String> { Binding( get: { presenter.answers.filledQuestions[question.text] ?? "" }, set: { presenter.answers.filledQuestions[question.text] = $0 } ) } }
ΠΡ Π±ΡΠ΄Π΅ΡΠ΅ ΡΠ΄ΠΈΠ²Π»Π΅Π½Ρ, Π½ΠΎ Π² ΠΏΡΠ΅Π·Π΅Π½ΡΠ΅ΡΠ΅ ΡΠ°ΠΊΠΆΠ΅ Π½Π΅Ρ Π½ΠΈΠΊΠ°ΠΊΠΎΠΉ Π±ΠΈΠ·Π½Π΅Ρ-Π»ΠΎΠ³ΠΈΠΊΠΈ!
class OnboardingPresenter: ObservableObject { @Published public var answers: FilledQuestionary private(set) public var questions: Questionary private var completion: (FilledQuestionary) -> Void init(questions: Questionary, answers: FilledQuestionary, completion: @escaping (FilledQuestionary) -> Void) { self.questions = questions self.answers = answers self.completion = completion } func save() { completion(answers) } }
ΠΡΠ΅ ΠΏΠΎ-ΠΏΡΠ΅ΠΆΠ½Π΅ΠΌΡ simple, stupid ΠΈ ΠΈΠΌΠ΅Π΅Ρ ΡΠΎΠ»ΡΠΊΠΎ ΠΎΠ΄Π½Ρ ΠΎΡΠ²Π΅ΡΡΡΠ²Π΅Π½Π½ΠΎΡΡΡ. Presenter Π΄ΠΎΠ»ΠΆΠ΅Π½ ΡΠΎΠ΄Π΅ΡΠΆΠ°ΡΡ ΡΠΎΠ»ΡΠΊΠΎ Π»ΠΎΠ³ΠΈΠΊΡ ΡΠ²ΠΎΠ΅Π³ΠΎ ΠΏΡΠ΅Π΄ΡΡΠ°Π²Π»Π΅Π½ΠΈΡ. ΠΠΈΠ·Π½Π΅Ρ-Π»ΠΎΠ³ΠΈΠΊΠ° ΡΡΠΎΠ²Π½Ρ ΠΏΡΠΈΠ»ΠΎΠΆΠ΅Π½ΠΈΡ Π½Π°Ρ ΠΎΠ΄ΠΈΡΡΡ Π²Π½Π΅ Π΅Π³ΠΎ ΡΡΠΈΡΠ΄ΠΈΠΊΡΠΈΠΈ, ΠΏΠΎΡΡΠΎΠΌΡ ΠΏΡΠ΅Π·Π΅Π½ΡΠ΅Ρ ΠΏΡΠΎΡΡΠΎ Π΄Π΅Π»Π΅Π³ΠΈΡΡΠ΅Ρ Π΅Π΅ Π½Π°Π²Π΅ΡΡ ΠΏΠΎ ΡΡΡΠΊΡ Π²ΡΠ·ΠΎΠ²Π°.
Π’Π°ΠΊΠΆΠ΅ ΠΌΠΎΠΆΠ½ΠΎ Π·Π°ΠΌΠ΅ΡΠΈΡΡ, ΡΡΠΎ ΠΈ View, ΠΈ Presenter Π½Π΅ ΠΈΠ½ΡΡΠ°Π½ΡΠΈΡΡΡΡ Π½ΠΈ ΠΎΠ΄Π½Ρ ΠΈΠ· Π·Π°Π²ΠΈΡΠΈΠΌΠΎΡΡΠ΅ΠΉ, Π° ΠΏΠΎΠ»ΡΡΠ°ΡΡ ΠΈΡ Π² ΠΊΠ°ΡΠ΅ΡΡΠ²Π΅ ΠΏΠ°ΡΠ°ΠΌΠ΅ΡΡΠΎΠ² ΠΏΡΠΈ ΠΈΠ½ΠΈΡΠΈΠ°Π»ΠΈΠ·Π°ΡΠΈΠΈ. ΠΡΠΎ ΡΠΎΠΎΡΠ²Π΅ΡΡΡΠ²ΡΠ΅Ρ ΠΏΡΠΈΠ½ΡΠΈΠΏΡ ΠΈΠ½Π²Π΅ΡΡΠΈΠΈ Π·Π°Π²ΠΈΡΠΈΠΌΠΎΡΡΠ΅ΠΉ, ΡΠΎΠ³Π»Π°ΡΠ½ΠΎ ΠΊΠΎΡΠΎΡΠΎΠΌΡ ΠΌΠΎΠ΄ΡΠ»ΠΈ Π²ΡΡΠΎΠΊΠΎΠ³ΠΎ ΡΡΠΎΠ²Π½Ρ Π½Π΅ Π΄ΠΎΠ»ΠΆΠ½Ρ Π·Π°Π²ΠΈΡΠ΅ΡΡ ΠΎΡ ΠΌΠΎΠ΄ΡΠ»Π΅ΠΉ Π½ΠΈΠ·ΠΊΠΎΠ³ΠΎ ΡΡΠΎΠ²Π½Ρ, Π½ΠΎ ΠΎΠ±Π° Π΄ΠΎΠ»ΠΆΠ½Ρ Π·Π°Π²ΠΈΡΠ΅ΡΡ ΠΎΡ Π°Π±ΡΡΡΠ°ΠΊΡΠΈΠΉ. ΠΡΠΎ ΠΎΠ±Π΅ΡΠΏΠ΅ΡΠΈΠ²Π°Π΅Ρ Π³ΠΈΠ±ΠΊΠΎΡΡΡ ΠΈ ΡΠΏΡΠΎΡΠ°Π΅Ρ ΡΠ΅ΡΡΠΈΡΠΎΠ²Π°Π½ΠΈΠ΅, Π° ΡΠ°ΠΊΠΆΠ΅ ΠΏΠΎΠ·Π²ΠΎΠ»ΡΠ΅Ρ Π»Π΅Π³ΠΊΠΎ Π·Π°ΠΌΠ΅Π½ΡΡΡ Π·Π°Π²ΠΈΡΠΈΠΌΠΎΡΡΠΈ ΠΈΠ»ΠΈ Π²Π½Π΅Π΄ΡΡΡΡ ΠΌΠ°ΠΊΠ΅ΡΡ Π΄Π»Ρ ΡΠ΅Π»Π΅ΠΉ ΡΠ΅ΡΡΠΈΡΠΎΠ²Π°Π½ΠΈΡ.
ΠΡΠΈ ΠΈΡΠΏΠΎΠ»ΡΠ·ΠΎΠ²Π°Π½ΠΈΠΈ ΠΏΠ°ΡΡΠ΅ΡΠ½Π° Dependency Injection Π·Π°Π²ΠΈΡΠΈΠΌΠΎΡΡΠΈ ΠΏΡΠ΅Π΄ΠΎΡΡΠ°Π²Π»ΡΡΡΡΡ ΠΈΠ·Π²Π½Π΅ ΠΊΠ»Π°ΡΡΠ°, Π° Π½Π΅ ΠΈΠ½ΡΡΠ°Π½ΡΠΈΡΡΡΡΡΡ Π²Π½ΡΡΡΠΈ Π½Π΅Π³ΠΎ. ΠΡΠΎ ΡΠΏΠΎΡΠΎΠ±ΡΡΠ²ΡΠ΅Ρ ΡΠ°Π·Π²ΡΠ·ΠΊΠ΅ ΠΈ ΠΏΠΎΠ·Π²ΠΎΠ»ΡΠ΅Ρ ΡΠΏΡΠΎΡΡΠΈΡΡ ΠΏΠΎΠ΄Π΄Π΅ΡΠΆΠΊΡ ΠΈ ΡΠ΅ΡΡΠΈΡΠΎΠ²Π°Π½ΠΈΠ΅ ΠΊΠΎΠ΄Π°.
Π₯ΠΎΡΡ Π² Π΄Π°Π½Π½ΠΎΠΌ ΠΏΡΠΈΠΌΠ΅ΡΠ΅ ΠΏΡΠΎΡΠΎΠΊΠΎΠ»Ρ Π½Π΅ ΠΈΡΠΏΠΎΠ»ΡΠ·ΡΡΡΡΡ ΡΠ²Π½ΠΎ, ΡΡΠΎΠΈΡ ΠΎΡΠΌΠ΅ΡΠΈΡΡ, ΡΡΠΎ ΠΏΡΠΎΡΠΎΠΊΠΎΠ»Ρ ΠΌΠΎΠ³ΡΡ ΠΈΠ³ΡΠ°ΡΡ Π²Π°ΠΆΠ½ΡΡ ΡΠΎΠ»Ρ Π² ΠΊΠΎΠ΄Π΅, ΠΎΡΠΎΠ±Π΅Π½Π½ΠΎ Π΄Π»Ρ Π°Π±ΡΡΡΠ°Π³ΠΈΡΠΎΠ²Π°Π½ΠΈΡ ΠΈ ΠΎΠ±Π»Π΅Π³ΡΠ΅Π½ΠΈΡ ΡΠ΅ΡΡΠΈΡΠΎΠ²Π°Π½ΠΈΡ. ΠΠΏΡΠ΅Π΄Π΅Π»ΠΈΠ² ΠΏΡΠΎΡΠΎΠΊΠΎΠ»Ρ Π΄Π»Ρ ΠΏΡΠ΅Π΄ΡΡΠ°Π²Π»Π΅Π½ΠΈΠΉ, ΠΏΡΠ΅Π·Π΅Π½ΡΠ°ΡΠΎΡΠΎΠ² ΠΈ Π·Π°Π²ΠΈΡΠΈΠΌΠΎΡΡΠ΅ΠΉ, ΡΡΠ°Π½ΠΎΠ²ΠΈΡΡΡ ΠΏΡΠΎΡΠ΅ Π·Π°ΠΌΠ΅Π½ΡΡΡ ΡΠ΅Π°Π»ΠΈΠ·Π°ΡΠΈΠΈ ΠΈΠ»ΠΈ ΠΏΡΠ΅Π΄ΠΎΡΡΠ°Π²Π»ΡΡΡ ΠΌΠΎΠΊΠΈ Π²ΠΎ Π²ΡΠ΅ΠΌΡ ΡΠ΅ΡΡΠΈΡΠΎΠ²Π°Π½ΠΈΡ.
ΠΡΠ°ΠΊ, Π΅ΡΠ»ΠΈ ΠΏΡΠ΅Π·Π΅Π½ΡΠ΅Ρ Π½Π΅ ΡΠΎΠ΄Π΅ΡΠΆΠΈΡ Π±ΠΈΠ·Π½Π΅Ρ-Π»ΠΎΠ³ΠΈΠΊΡ, ΡΠΎ Π³Π΄Π΅ ΠΆΠ΅ ΠΎΠ½Π°? ΠΡΠΎ Π·Π°Π΄Π°ΡΠ° Π΄Π»Ρ Π΄ΠΎΠΌΠ΅Π½Π½ΠΎΠ³ΠΎ ΡΠ»ΠΎΡ, ΠΊΠΎΡΠΎΡΡΠΉ ΠΎΠ±ΡΡΠ½ΠΎ ΡΠΎΠ΄Π΅ΡΠΆΠΈΡ ΡΠ΅ΡΠ²ΠΈΡΡ, ΠΏΡΠΎΠ²Π°ΠΉΠ΄Π΅ΡΡ ΠΈ ΠΌΠ΅Π½Π΅Π΄ΠΆΠ΅ΡΡ. Π£ Π½ΠΈΡ
Π²ΡΠ΅Ρ
ΠΎΡΠ΅Π½Ρ ΡΡ
ΠΎΠΆΠ΅Π΅ ΠΏΡΠΈΠΌΠ΅Π½Π΅Π½ΠΈΠ΅, ΠΈ ΡΠ°Π·Π½ΠΈΡΠ° ΠΌΠ΅ΠΆΠ΄Ρ Π½ΠΈΠΌΠΈ Π΄ΠΎ ΡΠΈΡ
ΠΏΠΎΡ ΡΠ²Π»ΡΠ΅ΡΡΡ ΠΏΡΠ΅Π΄ΠΌΠ΅ΡΠΎΠΌ Π΄ΠΈΡΠΊΡΡΡΠΈΠΉ. ΠΠ°Π²Π°ΠΉΡΠ΅ ΡΠΎΠ·Π΄Π°Π΄ΠΈΠΌ OnboardingProvider
, ΠΊΠΎΡΠΎΡΡΠΉ Π±ΡΠ΄Π΅Ρ ΡΠΎΠ΄Π΅ΡΠΆΠ°ΡΡ Π²ΡΡ Π±ΠΈΠ·Π½Π΅Ρ-Π»ΠΎΠ³ΠΈΠΊΡ ΠΏΡΠΎΡΠ΅ΡΡΠ° ΠΎΠ½Π±ΠΎΡΠ΄ΠΈΠ½Π³Π°.
class OnboardingProvider: ObservableObject { init() { loadFilledOnboardingFromDefaults() } // MARK: Interface @Published private(set) var needsOnboarding: Bool = true private(set) var filledOnboarding: FilledQuestionary? { didSet { if let filledOnboarding { saveFilledOnboardingToDefaults(filledQuestionary: filledOnboarding) } } } func getOnboardingQuestionary() -> Questionary { // NOTE: it's better to take the questions from the backend Questionary(questions: [ Question(text: "sex"), Question(text: "age"), Question(text: "weight"), Question(text: "height"), Question(text: "special_conditions"), ]) } func saveOnboardingAnswers(filledQuestionary: FilledQuestionary) { needsOnboarding = false filledOnboarding = filledQuestionary } // MARK: - Private private func saveFilledOnboardingToDefaults(filledQuestionary: FilledQuestionary) { UserDefaults.standard.removeObject(forKey: "filledOnboarding") let encoder = JSONEncoder() let encoded = try! encoder.encode(filledQuestionary) UserDefaults.standard.set(encoded, forKey: "filledOnboarding") } private func loadFilledOnboardingFromDefaults() { guard let object = UserDefaults.standard.object(forKey: "filledOnboarding") else { needsOnboarding = true return } let savedFilledQuestionary = object as! Data let decoder = JSONDecoder() let loadedQuestionary = try! decoder.decode(FilledQuestionary.self, from: savedFilledQuestionary) self.filledOnboarding = loadedQuestionary self.needsOnboarding = false } }
ΠΠΏΡΡΡ ΠΆΠ΅, ΠΎΠ½ Π²ΡΠΏΠΎΠ»Π½ΡΠ΅Ρ ΡΠΎΠ»ΡΠΊΠΎ ΠΎΠ΄Π½Ρ Π·Π°Π΄Π°ΡΡ: ΡΠΏΡΠ°Π²Π»Π΅Π½ΠΈΠ΅ Π±ΠΈΠ·Π½Π΅Ρ-Π»ΠΎΠ³ΠΈΠΊΠΎΠΉ ΠΏΡΠΎΡΠ΅ΡΡΠ° onboarding. Π’Π°ΠΊΠ°Ρ ΠΈΠ½ΠΊΠ°ΠΏΡΡΠ»ΡΡΠΈΡ ΠΏΠΎΠ·Π²ΠΎΠ»ΡΠ΅Ρ Π΄ΡΡΠ³ΠΈΠΌ ΠΊΠ»Π°ΡΡΠ°ΠΌ Π²Π·Π°ΠΈΠΌΠΎΠ΄Π΅ΠΉΡΡΠ²ΠΎΠ²Π°ΡΡ Ρ Π½ΠΈΠΌ Π±Π΅Π· Π½Π΅ΠΎΠ±Ρ ΠΎΠ΄ΠΈΠΌΠΎΡΡΠΈ Π±Π΅ΡΠΏΠΎΠΊΠΎΠΈΡΡΡΡ ΠΎ Π΄Π΅ΡΠ°Π»ΡΡ Π΅Π³ΠΎ Π²Π½ΡΡΡΠ΅Π½Π½Π΅ΠΉ ΡΠ΅Π°Π»ΠΈΠ·Π°ΡΠΈΠΈ, ΡΡΠΎ ΡΠΏΠΎΡΠΎΠ±ΡΡΠ²ΡΠ΅Ρ ΡΠΎΠ·Π΄Π°Π½ΠΈΡ Π±ΠΎΠ»Π΅Π΅ ΡΠΈΡΡΠΎΠΉ ΠΈ ΡΠ΄ΠΎΠ±Π½ΠΎΠΉ ΠΊΠΎΠ΄ΠΎΠ²ΠΎΠΉ Π±Π°Π·Ρ.
Π’Π΅ΠΏΠ΅ΡΡ Π΄Π°Π²Π°ΠΉΡΠ΅ ΡΠΎΠ±Π΅ΡΠ΅ΠΌ Π²ΡΠ΅ Π²ΠΌΠ΅ΡΡΠ΅ Π² ΠΊΠΎΡΠ½Π΅ ΠΏΡΠΈΠ»ΠΎΠΆΠ΅Π½ΠΈΡ.
import SwiftUI @main struct HouseMDAI: App { @StateObject private var onboardingProvider: OnboardingProvider @StateObject private var onboardingPresenter: OnboardingPresenter @StateObject private var homePresenter: HomePresenter init() { let onboardingProvider = OnboardingProvider() let onboardingPresenter = OnboardingPresenter( questions: onboardingProvider.getOnboardingQuestionary(), answers: FilledQuestionary(filledQuestions: [:]), completion: onboardingProvider.saveOnboardingAnswers ) let homePresenter = HomePresenter() _onboardingProvider = StateObject(wrappedValue: onboardingProvider) _onboardingPresenter = StateObject(wrappedValue: onboardingPresenter) _homePresenter = StateObject(wrappedValue: homePresenter) } var body: some Scene { WindowGroup { if onboardingProvider.needsOnboarding { OnboardingView(presenter: onboardingPresenter) } else { TabView { HomeView(presenter: homePresenter) if let profile = onboardingProvider.filledOnboarding { ProfileView(profile: profile) } } } } } // body }
ΠΡΠΎ ΠΏΡΠΈΠ»ΠΎΠΆΠ΅Π½ΠΈΠ΅ SwiftUI ΡΡΡΠ°Π½Π°Π²Π»ΠΈΠ²Π°Π΅Ρ ΡΠ²ΠΎΠ΅ Π½Π°ΡΠ°Π»ΡΠ½ΠΎΠ΅ ΡΠΎΡΡΠΎΡΠ½ΠΈΠ΅ Ρ ΠΏΠΎΠΌΠΎΡΡΡ ΠΎΠ±Π΅ΡΡΠΎΠΊ ΠΏΠΎΠ»Π΅ΠΉ StateObject
. ΠΠ½ΠΎ ΠΈΠ½ΠΈΡΠΈΠ°Π»ΠΈΠ·ΠΈΡΡΠ΅Ρ OnboardingProvider
, OnboardingPresenter
ΠΈ HomePresenter
Π² ΡΠ²ΠΎΠ΅ΠΌ ΠΌΠ΅ΡΠΎΠ΄Π΅ init. ΠΡΠΎΠ²Π°ΠΉΠ΄Π΅Ρ OnboardingProvider
ΠΎΡΠ²Π΅ΡΠ°Π΅Ρ Π·Π° ΡΠΏΡΠ°Π²Π»Π΅Π½ΠΈΠ΅ Π΄Π°Π½Π½ΡΠΌΠΈ, ΡΠ²ΡΠ·Π°Π½Π½ΡΠΌΠΈ Ρ ΠΎΠ½Π±ΠΎΡΠ΄ΠΈΠ½Π³ΠΎΠΌ, Π° OnboardingPresenter
ΡΠΏΡΠ°Π²Π»ΡΠ΅Ρ Π»ΠΎΠ³ΠΈΠΊΠΎΠΉ ΠΏΡΠ΅Π΄ΡΡΠ°Π²Π»Π΅Π½ΠΈΡ ΠΎΠ½Π±ΠΎΡΠ΄ΠΈΠ½Π³Π°. HomePresenter
ΡΠΏΡΠ°Π²Π»ΡΠ΅Ρ Π³Π»Π°Π²Π½ΡΠΌ Π΄ΠΎΠΌΠ°ΡΠ½ΠΈΠΌ ΠΏΡΠ΅Π΄ΡΡΠ°Π²Π»Π΅Π½ΠΈΠ΅ΠΌ.
Π ΡΠ΅Π»Π΅ ΡΡΠ΅Π½Ρ ΠΏΡΠΈΠ»ΠΎΠΆΠ΅Π½ΠΈΡ ΠΏΡΠΎΠ²Π΅ΡΡΠ΅ΡΡΡ, Π½ΡΠΆΠ½Π° Π»ΠΈ ΡΠ΅Π³ΠΈΡΡΡΠ°ΡΠΈΡ Π½Π° ΡΠ°ΠΉΡΠ΅. ΠΡΠ»ΠΈ Π΄Π°, ΡΠΎ ΠΎΠ½Π° ΠΏΡΠ΅Π΄ΡΡΠ°Π²Π»ΡΠ΅Ρ OnboardingView
Ρ OnboardingPresenter
. Π ΠΏΡΠΎΡΠΈΠ²Π½ΠΎΠΌ ΡΠ»ΡΡΠ°Π΅ ΠΎΠ½Π° ΠΏΡΠ΅Π΄ΡΡΠ°Π²Π»ΡΠ΅Ρ TabView
, ΡΠΎΠ΄Π΅ΡΠΆΠ°ΡΠΈΠΉ HomeView
Ρ HomePresenter
ΠΈ, Π΅ΡΠ»ΠΈ Π΄ΠΎΡΡΡΠΏΠ½ΠΎ, ProfileView
.
Π’Π΅ΠΏΠ΅ΡΡ Π½Π°ΡΡΠ°Π»ΠΎ Π²ΡΠ΅ΠΌΡ Π΄Π»Ρ Π΄ΠΎΠΌΠ°ΡΠ½Π΅Π³ΠΎ ΠΏΡΠ΅Π΄ΡΡΠ°Π²Π»Π΅Π½ΠΈΡ. ΠΠΎΠ³ΠΈΠΊΠ° ΠΏΡΠΎΡΡΠ°:
- ΠΠΎΠ»ΡΡΠ°Π΅ΠΌ ΡΠΎΠΎΠ±ΡΠ΅Π½ΠΈΠ΅ ΠΎΡ ΠΏΠΎΠ»ΡΠ·ΠΎΠ²Π°ΡΠ΅Π»Ρ.
- ΠΡΠΏΠΎΠ»ΡΠ·ΡΡ ΡΠΎΠΎΠ±ΡΠ΅Π½ΠΈΠ΅, Π·Π°ΠΏΡΠ°ΡΠΈΠ²Π°Π΅ΠΌ ΡΠΏΠΈΡΠΎΠΊ Π²ΠΎΠΏΡΠΎΡΠΎΠ² ΠΈΠ· Π±ΡΠΊΠ΅Π½Π΄Π°.
- ΠΠΎΠΊΠ°Π·ΡΠ²Π°Π΅ΠΌ Π²ΠΎΠΏΡΠΎΡΡ ΠΏΠΎ ΠΎΠ΄Π½ΠΎΠΌΡ, ΠΈΡΠΏΠΎΠ»ΡΠ·ΡΡ Π²ΡΡΡΠΎΠ΅Π½Π½ΡΡ push-Π½Π°Π²ΠΈΠ³Π°ΡΠΈΡ.
- ΠΠΎΠ±Π°Π²Π»ΡΠ΅ΠΌ ΠΎΡΠ²Π΅ΡΡ ΠΊ Π·Π°ΠΏΡΠΎΡΡ ΠΈ ΠΏΠΎΠ²ΡΠΎΡΡΠ΅ΠΌ 2-4, ΠΏΠΎΠΊΠ° Π±ΡΠΊΠ΅Π½Π΄-Π΄ΠΎΠΊΡΠΎΡ Π½Π΅ Π²Π΅ΡΠ½Π΅Ρ ΠΎΠΊΠΎΠ½ΡΠ°ΡΠ΅Π»ΡΠ½ΡΠΉ ΡΠ΅Π·ΡΠ»ΡΡΠ°Ρ.
- ΠΠΎΠΊΠ°Π·ΡΠ²Π°Π΅ΠΌ ΡΠΈΠ½Π°Π»ΡΠ½ΡΠΉ ΡΠ΅Π·ΡΠ»ΡΡΠ°Ρ.
struct HomeView: View { @StateObject var presenter: HomePresenter var body: some View { NavigationStack(path: $presenter.navigationPath) { VStack { // 1 Text("How are you?") TextField("...", text: $presenter.message) .lineLimit(5...10) .formItem() // 2 Button("Send", action: presenter.onSend) } .padding() .navigationDestination(for: NavigationPage.self) { page in switch page { case .questinary(let questions, let answers): // 3 QuestionaryView( presenter: QuestionaryPresenter( questions: questions, answers: answers, completion: presenter.onQuestionaryFilled ) ) case .answer(let string): // 5 VStack { Text("The doctor says...") Text(string) .font(.title2) .padding() } } } } } }
ΠΠΎΡ ΠΎΠΆΠ΅, Ρ ΠΏΡΠΎΠΏΡΡΡΠΈΠ» 4-ΠΉ ΠΏΡΠ½ΠΊΡ... ΠΈΠ»ΠΈ Π½Π΅Ρ? ΠΠΎΡΠΊΠΎΠ»ΡΠΊΡ ΠΏΡΠ΅Π΄ΡΡΠ°Π²Π»Π΅Π½ΠΈΠ΅ Π½Π΅ ΠΌΠΎΠΆΠ΅Ρ ΡΠΎΠ΄Π΅ΡΠΆΠ°ΡΡ Π½ΠΈΠΊΠ°ΠΊΠΎΠΉ Π»ΠΎΠ³ΠΈΠΊΠΈ, ΡΡΡ ΡΠ°ΡΡΡ Π²ΡΠΏΠΎΠ»Π½ΡΠ΅Ρ Π΅Π³ΠΎ ΠΏΡΠ΅Π·Π΅Π½ΡΠ΅Ρ.
enum NavigationPage: Hashable { case questinary(Questionary, FilledQuestionary) case answer(String) } class HomePresenter: ObservableObject { @Published var message: String = "" @Published var navigationPath: [NavigationPage] = [] init(message: String = "") { self.message = message } func onSend() { Task { let doctor = DoctorProvider() let answer = try! await doctor.sendMessage(message: message) switch answer { case .questions(let questions): navigationPath.append(.questinary(questions, FilledQuestionary(filledQuestions: [:]))) case .answer(let string): navigationPath.append(.answer(string)) } } } func onQuestionaryFilled(filled: FilledQuestionary) { Task { let doctor = DoctorProvider() let answer = try! await doctor.sendAnswers(message: message, answers: filled) switch answer { case .questions(let newQuestions): navigationPath.append(.questinary(newQuestions, filled)) case .answer(let string): navigationPath.append(.answer(string)) } } } }
ΠΠ½ ΡΠΏΡΠ°Π²Π»ΡΠ΅Ρ Π²Π²ΠΎΠ΄ΠΎΠΌ ΡΠΎΠΎΠ±ΡΠ΅Π½ΠΈΡ ΠΏΠΎΠ»ΡΠ·ΠΎΠ²Π°ΡΠ΅Π»Π΅ΠΌ ΠΈ ΠΎΠ±Π½ΠΎΠ²Π»ΡΠ΅Ρ ΠΏΡΡΡ Π½Π°Π²ΠΈΠ³Π°ΡΠΈΠΈ Π½Π° ΠΎΡΠ½ΠΎΠ²Π΅ ΠΎΡΠ²Π΅ΡΠΎΠ² ΠΎΡ Π±ΡΠΊΠ΅Π½Π΄Π°.
ΠΡΠΈ ΠΎΡΠΏΡΠ°Π²ΠΊΠ΅ ΡΠΎΠΎΠ±ΡΠ΅Π½ΠΈΡ ΠΌΠ΅ΡΠΎΠ΄ onSend()
ΠΎΡΠΏΡΠ°Π²Π»ΡΠ΅Ρ Π΅Π³ΠΎ Π½Π° Π±ΡΠΊΠ΅Π½Π΄ Ρ ΠΏΠΎΠΌΠΎΡΡΡ DoctorProvider
ΠΈ ΠΎΠΆΠΈΠ΄Π°Π΅Ρ ΠΎΡΠ²Π΅ΡΠ°. Π Π·Π°Π²ΠΈΡΠΈΠΌΠΎΡΡΠΈ ΠΎΡ ΡΠΈΠΏΠ° ΠΎΡΠ²Π΅ΡΠ° ΠΎΠ½ ΠΎΠ±Π½ΠΎΠ²Π»ΡΠ΅Ρ Π½Π°Π²ΠΈΠ³Π°ΡΠΈΠΎΠ½Π½ΡΠΉ ΠΏΡΡΡ, ΠΎΡΠΎΠ±ΡΠ°ΠΆΠ°Ρ Π»ΠΈΠ±ΠΎ Π½Π°Π±ΠΎΡ Π²ΠΎΠΏΡΠΎΡΠΎΠ², Π»ΠΈΠ±ΠΎ ΠΎΠΊΠΎΠ½ΡΠ°ΡΠ΅Π»ΡΠ½ΡΠΉ ΠΎΡΠ²Π΅Ρ.
ΠΠ½Π°Π»ΠΎΠ³ΠΈΡΠ½ΠΎ, ΠΊΠΎΠ³Π΄Π° Π·Π°ΠΏΠΎΠ»Π½ΡΠ΅ΡΡΡ Π²ΠΎΠΏΡΠΎΡΠ½ΠΈΠΊ, ΠΌΠ΅ΡΠΎΠ΄ onQuestionaryFilled()
ΠΎΡΠΏΡΠ°Π²Π»ΡΠ΅Ρ Π·Π°ΠΏΠΎΠ»Π½Π΅Π½Π½ΡΠΉ Π²ΠΎΠΏΡΠΎΡΠ½ΠΈΠΊ Π½Π° Π±ΡΠΊΠ΅Π½Π΄ ΠΈ ΡΠΎΠΎΡΠ²Π΅ΡΡΡΠ²ΡΡΡΠΈΠΌ ΠΎΠ±ΡΠ°Π·ΠΎΠΌ ΠΎΠ±Π½ΠΎΠ²Π»ΡΠ΅Ρ ΠΏΡΡΡ Π½Π°Π²ΠΈΠ³Π°ΡΠΈΠΈ.
ΠΠ΄Π΅ΡΡ Π΅ΡΡΡ Π½Π΅Π±ΠΎΠ»ΡΡΠΎΠ΅ Π΄ΡΠ±Π»ΠΈΡΠΎΠ²Π°Π½ΠΈΠ΅ ΠΊΠΎΠ΄Π° ΠΌΠ΅ΠΆΠ΄Ρ ΠΌΠ΅ΡΠΎΠ΄Π°ΠΌΠΈ onSend()
ΠΈ onQuestionaryFilled()
, ΠΊΠΎΡΠΎΡΠΎΠ΅ ΠΌΠΎΠΆΠ½ΠΎ Π±ΡΠ»ΠΎ Π±Ρ ΠΎΡΡΠ΅ΡΠ°ΠΊΡΠΎΡΠΈΠ·ΠΎΠ²Π°ΡΡ Π² ΠΎΠ΄ΠΈΠ½ ΠΌΠ΅ΡΠΎΠ΄ Π΄Π»Ρ ΠΎΠ±ΡΠ°Π±ΠΎΡΠΊΠΈ ΠΎΠ±ΠΎΠΈΡ
ΡΠ»ΡΡΠ°Π΅Π². ΠΠ΄Π½Π°ΠΊΠΎ ΠΎΡΡΠ°Π²ΠΈΠΌ ΡΡΠΎ ΠΊΠ°ΠΊ ΡΠΏΡΠ°ΠΆΠ½Π΅Π½ΠΈΠ΅ Π΄Π»Ρ Π΄Π°Π»ΡΠ½Π΅ΠΉΡΠ΅ΠΉ Π΄ΠΎΡΠ°Π±ΠΎΡΠΊΠΈ.
ΠΠΎΠ΄ΡΠ»Ρ Questionary (View+Presenter) ΠΏΠΎΡΡΠΈ ΡΠ²Π»ΡΠ΅ΡΡΡ ΠΊΠΎΠΏΠΈΠ΅ΠΉ Onboarding ΠΈ ΠΏΡΠΎΡΡΠΎ Π΄Π΅Π»Π΅Π³ΠΈΡΡΠ΅Ρ Π»ΠΎΠ³ΠΈΠΊΡ Π΄ΠΎ HomePresenter
, ΠΏΠΎΡΡΠΎΠΌΡ Ρ Π½Π΅ Π²ΠΈΠΆΡ Π½Π΅ΠΎΠ±Ρ
ΠΎΠ΄ΠΈΠΌΠΎΡΡΠΈ ΠΏΠΎΠΊΠ°Π·ΡΠ²Π°ΡΡ ΠΊΠΎΠ΄. ΠΠΏΡΡΡ ΠΆΠ΅, Π΄Π»Ρ ΡΡΠΎΠ³ΠΎ Π΅ΡΡΡ github.
ΠΠΎΡΠ»Π΅Π΄Π½Π΅Π΅, ΡΡΠΎ Ρ Ρ
ΠΎΡΡ ΠΏΠΎΠΊΠ°Π·Π°ΡΡ, ΡΡΠΎ Π΄Π²Π΅ ΡΠ΅Π°Π»ΠΈΠ·Π°ΡΠΈΠΈ DoctorProvider
, Π΅Π΄ΠΈΠ½ΡΡΠ²Π΅Π½Π½ΠΎΠΉ ΠΎΠ±ΡΠ·Π°Π½Π½ΠΎΡΡΡΡ ΠΊΠΎΡΠΎΡΡΡ
ΡΠ²Π»ΡΠ΅ΡΡΡ Π²ΡΠ·ΠΎΠ² API ΠΈ Π²ΠΎΠ·Π²ΡΠ°Ρ DoctorResponse
. ΠΠ΅ΡΠ²Π°Ρ ΠΈΡΠΏΠΎΠ»ΡΠ·ΡΠ΅Ρ Π½Π°Ρ Π±ΡΠΊΠ΅Π½Π΄.
import Alamofire enum DoctorResponse { case questions(Questionary) case answer(String) init(from string: String) throws { if let data = string.data(using: .utf8) { if string.contains("\"questions\""){ let decoded = try! JSONDecoder().decode(Questionary.self, from: data) self = .questions(decoded) } else if string.contains("\"text\"") { let decoded = try! JSONDecoder().decode(Answer.self, from: data) self = .answer(decoded.text) } else { throw NSError(domain: "DoctorResponseError", code: 0, userInfo: [NSLocalizedDescriptionKey: "Unknown response format"]) } } else { throw NSError(domain: "DoctorResponseError", code: 1, userInfo: [NSLocalizedDescriptionKey: "Invalid string encoding"]) } } } class DoctorProvider { private let baseUrl = "" func sendMessage(message: String) async throws -> DoctorResponse { try! await sendAnswers(message: message, answers: FilledQuestionary(filledQuestions: [:])) } func sendAnswers(message: String, answers: FilledQuestionary) async throws -> DoctorResponse { struct DoctorParams: Codable { var message: String var userCard: [String: String] var filledQuestionary: FilledQuestionary } let onboard = OnboardingProvider() let paramsObject = DoctorParams( message: message, userCard: onboard.filledOnboarding!.filledQuestions, filledQuestionary: answers ) let encoder = JSONParameterEncoder.default encoder.encoder.keyEncodingStrategy = .convertToSnakeCase let responseString = try await AF.request( baseUrl + "/doctor", method: .post, parameters: paramsObject, encoder: encoder ).serializingString().value return try! DoctorResponse(from: responseString) } }
ΠΡΠΎΡΠ°Ρ Π²ΡΠ·ΡΠ²Π°Π΅Ρ openai api Π½Π°ΠΏΡΡΠΌΡΡ (ΠΏΠΎΠ΄Ρ ΠΎΠ΄ backendless) ΠΈ ΡΠ²Π»ΡΠ΅ΡΡΡ ΠΏΡΠ°ΠΊΡΠΈΡΠ΅ΡΠΊΠΈ ΠΊΠΎΠΏΠΈΠ΅ΠΉ ΠΌΠΎΠ΄ΡΠ»Ρ ΠΏΠΎΠ΄ΡΠΊΠ°Π·ΠΎΠΊ ΠΈΠ· Π±ΡΠΊΠ΅Π½Π΄Π°.
class PromptsProvider { private(set) public var homeRole = "" // TODO: take from the backend func message(message: String) -> String { return message } func profile(profile: FilledQuestionary) -> String { return try! jsonify(object: profile) } func answers(filled: FilledQuestionary) -> String { return try! jsonify(object: filled) } // MARK: - Private private func jsonify(object: Encodable) throws -> String { let coder = JSONEncoder() return String(data: try coder.encode(object), encoding: .utf8) ?? "" } } class HouseMDAIProvider { private var openAI: OpenAI init() { openAI = OpenAI(apiToken: "") } func sendMessage(message: String) async throws -> DoctorResponse { try! await sendAnswers(message: message, answers: FilledQuestionary(filledQuestions: [:])) } func sendAnswers(message: String, answers: FilledQuestionary) async throws -> DoctorResponse { // NOTE: Draft version, DI should be used instead! let promptProvider = PromptsProvider() let profile = OnboardingProvider().filledOnboarding! let query = ChatQuery(model: .gpt3_5Turbo, messages: [ Chat(role: .system, content: promptProvider.homeRole), Chat(role: .user, content: promptProvider.profile(profile: profile)), Chat(role: .user, content: promptProvider.message(message: message)), Chat(role: .user, content: promptProvider.answers(filled: answers)), ]) let result = try await openAI.chats(query: query) return try! DoctorResponse(from: result.choices[0].message.content ?? "") } }
ΠΡΡΠ³ΠΎΠΉ ΠΏΡΠΈΠΌΠ΅Ρ
ΠΠΎΡΠΌΠΎΡΡΠ΅ΡΡ ΠΏΡΠΈΠΌΠ΅Ρ ΡΡΠΎΠΉ Π°ΡΡ ΠΈΡΠ΅ΠΊΡΡΡΡ Π² ΡΠ΅Π°Π»ΡΠ½ΠΎΠΌ ΠΏΡΠΈΠ»ΠΎΠΆΠ΅Π½ΠΈΠΈ ΠΌΠΎΠΆΠ½ΠΎ Π² ΠΌΠΎΠ΅ΠΌ ΠΏΡΠΎΠ΅ΠΊΡΠ΅ TwiTreads Π½Π° github.com/MarkParker5/TwiTreads
Π§ΡΠΎ Π΄Π΅Π»Π°ΡΡ Π΄Π°Π»ΡΡΠ΅
- ΠΠ½ΡΠ΅Π³ΡΠΈΡΡΠΉΡΠ΅ Π°ΡΡΠ΅Π½ΡΠΈΡΠΈΠΊΠ°ΡΠΈΡ ΠΈ Π±Π°Π·Ρ Π΄Π°Π½Π½ΡΡ ΠΏΠΎΠ»ΡΠ·ΠΎΠ²Π°ΡΠ΅Π»Π΅ΠΉ Π² Π±ΡΠΊΠ΅Π½Π΄. ΠΠΎΠΆΠ΅ΡΠ΅ ΠΈΡΠΏΠΎΠ»ΡΠ·ΠΎΠ²Π°ΡΡ ΠΎΡΠΈΡΠΈΠ°Π»ΡΠ½ΡΠΉ ΡΠ°Π±Π»ΠΎΠ½ FastAPI ΠΈΠ· FastAPI Project Generation.
- Π Π΅Π°Π»ΠΈΠ·ΡΠΉΡΠ΅ Π»ΠΎΠ³ΠΈΠΊΡ Π°ΡΡΠ΅Π½ΡΠΈΡΠΈΠΊΠ°ΡΠΈΠΈ Π² ΠΏΡΠΈΠ»ΠΎΠΆΠ΅Π½ΠΈΠΈ.
- Π‘ΠΎΡΡΠ΅Π΄ΠΎΡΠΎΡΡΡΠ΅ΡΡ Π½Π° ΡΠ»ΡΡΡΠ΅Π½ΠΈΠΈ Π΄ΠΈΠ·Π°ΠΉΠ½Π° ΠΏΡΠΈΠ»ΠΎΠΆΠ΅Π½ΠΈΡ, ΡΡΠΎΠ±Ρ ΠΏΠΎΠ²ΡΡΠΈΡΡ ΡΠ΄ΠΎΠ±ΡΡΠ²ΠΎ ΡΠ°Π±ΠΎΡΡ Ρ Π½ΠΈΠΌ. ΠΠ°Π²Π°ΠΉΡΠ΅ ΡΠΎΠ·Π΄Π°Π²Π°ΡΡ ΠΊΡΠ°ΡΠΈΠ²ΡΠ΅ ΠΏΡΠΈΠ»ΠΎΠΆΠ΅Π½ΠΈΡ!
ΠΠ°ΠΊΠ»ΡΡΠ΅Π½ΠΈΠ΅
ΠΡΠΈΠ²Π΅Π΄Π΅Π½Π½ΡΠ΅ ΠΏΡΠΎΠ΅ΠΊΡΡ ΠΈ ΡΡΡΠ»ΠΊΠΈ Π½Π° ΠΊΠΎΠ΄ ΡΠ»ΡΠΆΠ°Ρ ΡΠ΅Π°Π»ΡΠ½ΡΠΌΠΈ ΠΏΡΠΈΠΌΠ΅ΡΠ°ΠΌΠΈ, ΡΡΠΎΠ±Ρ Π΄Π°ΡΡ ΡΠΎΠ»ΡΠΎΠΊ Π²Π°ΡΠ΅ΠΉ ΡΠΎΠ±ΡΡΠ²Π΅Π½Π½ΠΎΠΉ ΡΠ°Π·ΡΠ°Π±ΠΎΡΠΊΠ΅. ΠΠΎΠΌΠ½ΠΈΡΠ΅, ΡΡΠΎ ΠΊΡΠ°ΡΠΎΡΠ° ΡΠ΅Ρ Π½ΠΎΠ»ΠΎΠ³ΠΈΠΈ Π·Π°ΠΊΠ»ΡΡΠ°Π΅ΡΡΡ Π² ΠΈΡΠ΅ΡΠ°ΡΠΈΡΡ . ΠΠ°ΡΠ½ΠΈΡΠ΅ Ρ ΠΏΡΠΎΡΡΠΎΠ³ΠΎ: ΡΠΎΠ·Π΄Π°ΠΉΡΠ΅ ΠΏΡΠΎΡΠΎΡΠΈΠΏ ΠΈ ΠΏΠΎΡΡΠΎΡΠ½Π½ΠΎ ΡΠΎΠ²Π΅ΡΡΠ΅Π½ΡΡΠ²ΡΠΉΡΠ΅ Π΅Π³ΠΎ. ΠΠ°ΠΆΠ΄ΡΠΉ ΡΠ°Π³ Π²ΠΏΠ΅ΡΠ΅Π΄ ΠΏΡΠΈΠ±Π»ΠΈΠΆΠ°Π΅Ρ Π²Π°Ρ ΠΊ ΠΎΠ²Π»Π°Π΄Π΅Π½ΠΈΡ ΠΈΡΠΊΡΡΡΡΠ²ΠΎΠΌ ΡΠ°Π·ΡΠ°Π±ΠΎΡΠΊΠΈ ΠΏΡΠΎΠ³ΡΠ°ΠΌΠΌΠ½ΠΎΠ³ΠΎ ΠΎΠ±Π΅ΡΠΏΠ΅ΡΠ΅Π½ΠΈΡ ΠΈ, Π²ΠΎΠ·ΠΌΠΎΠΆΠ½ΠΎ, ΠΊ ΡΠ»Π΅Π΄ΡΡΡΠ΅ΠΌΡ Π±ΠΎΠ»ΡΡΠΎΠΌΡ ΠΏΡΠΎΡΡΠ²Ρ Π² ΡΠ΅Ρ Π½ΠΎΠ»ΠΎΠ³ΠΈΡΡ . Π‘ΡΠ°ΡΡΠ»ΠΈΠ²ΠΎΠ³ΠΎ ΠΊΠΎΠ΄ΠΈΠ½Π³Π°!
ΠΠ²ΡΠΎΡ: ΠΠ°ΡΠΊ ΠΠ°ΡΠΊΠ΅Ρ
Π’Π΅Π»Π΅Π³ΡΠ°ΠΌ-ΠΊΠ°Π½Π°Π»: t.me/parker_is_typing