622 lines
22 KiB
Python
622 lines
22 KiB
Python
1|"""
|
||
2|Pydantic Models для City Backend
|
||
3|"""
|
||
4|
|
||
5|from pydantic import BaseModel, Field
|
||
6|from typing import Optional, List, Dict, Any
|
||
7|from datetime import datetime
|
||
8|
|
||
9|
|
||
10|# =============================================================================
|
||
11|# City Rooms
|
||
12|# =============================================================================
|
||
13|
|
||
14|class CityRoomBase(BaseModel):
|
||
15| slug: str
|
||
16| name: str
|
||
17| description: Optional[str] = None
|
||
18|
|
||
19|
|
||
20|class CityRoomCreate(CityRoomBase):
|
||
21| pass
|
||
22|
|
||
23|
|
||
24|class CityRoomRead(CityRoomBase):
|
||
25| id: str
|
||
26| is_default: bool
|
||
27| created_at: datetime
|
||
28| created_by: Optional[str] = None
|
||
29| members_online: int = 0
|
||
30| last_event: Optional[str] = None
|
||
31| # Branding
|
||
32| logo_url: Optional[str] = None
|
||
33| banner_url: Optional[str] = None
|
||
34| # Context
|
||
35| microdao_id: Optional[str] = None
|
||
36| microdao_name: Optional[str] = None
|
||
37| microdao_slug: Optional[str] = None
|
||
38| microdao_logo_url: Optional[str] = None
|
||
39| # Matrix integration
|
||
40| matrix_room_id: Optional[str] = None
|
||
41| matrix_room_alias: Optional[str] = None
|
||
42|
|
||
43|
|
||
44|# =============================================================================
|
||
45|# City Room Messages
|
||
46|# =============================================================================
|
||
47|
|
||
48|class CityRoomMessageBase(BaseModel):
|
||
49| body: str = Field(..., min_length=1, max_length=10000)
|
||
50|
|
||
51|
|
||
52|class CityRoomMessageCreate(CityRoomMessageBase):
|
||
53| pass
|
||
54|
|
||
55|
|
||
56|class CityRoomMessageRead(CityRoomMessageBase):
|
||
57| id: str
|
||
58| room_id: str
|
||
59| author_user_id: Optional[str] = None
|
||
60| author_agent_id: Optional[str] = None
|
||
61| username: Optional[str] = "Anonymous" # Для frontend
|
||
62| created_at: datetime
|
||
63|
|
||
64|
|
||
65|# =============================================================================
|
||
66|# City Room Detail (з повідомленнями)
|
||
67|# =============================================================================
|
||
68|
|
||
69|class CityRoomDetail(CityRoomRead):
|
||
70| messages: List[CityRoomMessageRead] = []
|
||
71| online_members: List[str] = [] # user_ids
|
||
72|
|
||
73|
|
||
74|# =============================================================================
|
||
75|# City Feed Events
|
||
76|# =============================================================================
|
||
77|
|
||
78|class CityFeedEventRead(BaseModel):
|
||
79| id: str
|
||
80| kind: str # 'room_message', 'agent_reply', 'system', 'dao_event'
|
||
81| room_id: Optional[str] = None
|
||
82| user_id: Optional[str] = None
|
||
83| agent_id: Optional[str] = None
|
||
84| payload: dict
|
||
85| created_at: datetime
|
||
86|
|
||
87|
|
||
88|# =============================================================================
|
||
89|# Presence
|
||
90|# =============================================================================
|
||
91|
|
||
92|class PresenceUpdate(BaseModel):
|
||
93| user_id: str
|
||
94| status: str # 'online', 'offline', 'away'
|
||
95| last_seen: Optional[datetime] = None
|
||
96|
|
||
97|
|
||
98|class PresenceBulkUpdate(BaseModel):
|
||
99| users: List[PresenceUpdate]
|
||
100|
|
||
101|
|
||
102|# =============================================================================
|
||
103|# WebSocket Messages
|
||
104|# =============================================================================
|
||
105|
|
||
106|class WSRoomMessage(BaseModel):
|
||
107| event: str # 'room.message', 'room.join', 'room.leave'
|
||
108| room_id: Optional[str] = None
|
||
109| user_id: Optional[str] = None
|
||
110| message: Optional[CityRoomMessageRead] = None
|
||
111|
|
||
112|
|
||
113|class WSPresenceMessage(BaseModel):
|
||
114| event: str # 'presence.heartbeat', 'presence.update'
|
||
115| user_id: str
|
||
116| status: Optional[str] = None
|
||
117|
|
||
118|
|
||
119|# =============================================================================
|
||
120|# City Map (2D Map)
|
||
121|# =============================================================================
|
||
122|
|
||
123|class CityMapRoom(BaseModel):
|
||
124| """Room representation on 2D city map"""
|
||
125| id: str
|
||
126| slug: str
|
||
127| name: str
|
||
128| description: Optional[str] = None
|
||
129| room_type: str = "public"
|
||
130| zone: str = "central"
|
||
131| icon: Optional[str] = None
|
||
132| color: Optional[str] = None
|
||
133| # Map coordinates
|
||
134| x: int = 0
|
||
135| y: int = 0
|
||
136| w: int = 1
|
||
137| h: int = 1
|
||
138| # Matrix integration
|
||
139| matrix_room_id: Optional[str] = None
|
||
140|
|
||
141|
|
||
142|class CityMapConfig(BaseModel):
|
||
143| """Global city map configuration"""
|
||
144| grid_width: int = 6
|
||
145| grid_height: int = 3
|
||
146| cell_size: int = 100
|
||
147| background_url: Optional[str] = None
|
||
148|
|
||
149|
|
||
150|class CityMapResponse(BaseModel):
|
||
151| """Full city map response"""
|
||
152| config: CityMapConfig
|
||
153| rooms: List[CityMapRoom]
|
||
154|
|
||
155|
|
||
156|# =============================================================================
|
||
157|# Branding & Assets
|
||
158|# =============================================================================
|
||
159|
|
||
160|class BrandingUpdatePayload(BaseModel):
|
||
161| logo_url: Optional[str] = None
|
||
162| banner_url: Optional[str] = None
|
||
163|
|
||
164|
|
||
165|class AssetUploadResponse(BaseModel):
|
||
166| original_url: str
|
||
167| processed_url: str
|
||
168| thumb_url: Optional[str] = None
|
||
169|
|
||
170|
|
||
171|# =============================================================================
|
||
172|# Agents (for Agent Presence)
|
||
173|# =============================================================================
|
||
174|
|
||
175|class AgentRead(BaseModel):
|
||
176| """Agent representation"""
|
||
177| id: str
|
||
178| display_name: str
|
||
179| kind: str = "assistant" # assistant, civic, oracle, builder
|
||
180| avatar_url: Optional[str] = None
|
||
181| color: str = "cyan"
|
||
182| status: str = "offline" # online, offline, busy
|
||
183| current_room_id: Optional[str] = None
|
||
184| capabilities: List[str] = []
|
||
185|
|
||
186|
|
||
187|class AgentPresence(BaseModel):
|
||
188| """Agent presence in a room"""
|
||
189| agent_id: str
|
||
190| display_name: str
|
||
191| kind: str
|
||
192| status: str
|
||
193| room_id: Optional[str] = None
|
||
194| color: Optional[str] = None
|
||
195| node_id: Optional[str] = None
|
||
196| district: Optional[str] = None
|
||
197| model: Optional[str] = None
|
||
198| role: Optional[str] = None
|
||
199| avatar_url: Optional[str] = None
|
||
200|
|
||
201|
|
||
202|# =============================================================================
|
||
203|# Citizens
|
||
204|# =============================================================================
|
||
205|
|
||
206|class CityPresenceRoomView(BaseModel):
|
||
207| room_id: Optional[str] = None
|
||
208| slug: Optional[str] = None
|
||
209| name: Optional[str] = None
|
||
210|
|
||
211|
|
||
212|class CityPresenceView(BaseModel):
|
||
213| primary_room_slug: Optional[str] = None
|
||
214| rooms: List[CityPresenceRoomView] = []
|
||
215|
|
||
216|
|
||
217|class HomeNodeView(BaseModel):
|
||
218| """Home node information for agent/citizen"""
|
||
219| id: Optional[str] = None
|
||
220| name: Optional[str] = None
|
||
221| hostname: Optional[str] = None
|
||
222| roles: List[str] = []
|
||
223| environment: Optional[str] = None
|
||
224|
|
||
225|
|
||
226|class NodeAgentSummary(BaseModel):
|
||
227| """Summary of a node agent (Guardian or Steward)"""
|
||
228| id: str
|
||
229| name: Optional[str] = None
|
||
230| kind: Optional[str] = None
|
||
231| slug: Optional[str] = None
|
||
232|
|
||
233|
|
||
234|class NodeMicrodaoSummary(BaseModel):
|
||
235| """Summary of a MicroDAO hosted on a node (via orchestrator)"""
|
||
236| id: str
|
||
237| slug: str
|
||
238| name: str
|
||
239| rooms_count: int = 0
|
||
240|
|
||
241|
|
||
242|class NodeMetrics(BaseModel):
|
||
243| """Node metrics for Node Directory cards"""
|
||
244| cpu_model: Optional[str] = None
|
||
245| cpu_cores: int = 0
|
||
246| cpu_usage: float = 0.0
|
||
247| gpu_model: Optional[str] = None
|
||
248| gpu_vram_total: int = 0
|
||
249| gpu_vram_used: int = 0
|
||
250| ram_total: int = 0
|
||
251| ram_used: int = 0
|
||
252| disk_total: int = 0
|
||
253| disk_used: int = 0
|
||
254| agent_count_router: int = 0
|
||
255| agent_count_system: int = 0
|
||
256| dagi_router_url: Optional[str] = None
|
||
257| swapper_healthy: bool = False
|
||
258| swapper_models_loaded: int = 0
|
||
259| swapper_models_total: int = 0
|
||
260|
|
||
261|
|
||
262|class NodeProfile(BaseModel):
|
||
263| """Node profile for Node Directory"""
|
||
264| node_id: str
|
||
265| name: str
|
||
266| hostname: Optional[str] = None
|
||
267| roles: List[str] = []
|
||
268| environment: str = "unknown"
|
||
269| status: str = "offline"
|
||
270| gpu_info: Optional[str] = None
|
||
271| agents_total: int = 0
|
||
272| agents_online: int = 0
|
||
273| last_heartbeat: Optional[str] = None
|
||
274| guardian_agent_id: Optional[str] = None
|
||
275| steward_agent_id: Optional[str] = None
|
||
276| guardian_agent: Optional[NodeAgentSummary] = None
|
||
277| steward_agent: Optional[NodeAgentSummary] = None
|
||
278| microdaos: List[NodeMicrodaoSummary] = []
|
||
279| metrics: Optional[NodeMetrics] = None
|
||
280|
|
||
281|
|
||
282|class ModelBindings(BaseModel):
|
||
283| """Agent model bindings for AI capabilities"""
|
||
284| primary_model: Optional[str] = None # e.g., "qwen3:8b"
|
||
285| supported_kinds: List[str] = [] # e.g., ["text", "vision", "audio"]
|
||
286|
|
||
287|
|
||
288|class UsageStats(BaseModel):
|
||
289| """Agent usage statistics"""
|
||
290| tokens_total_24h: Optional[int] = None
|
||
291| calls_total_24h: Optional[int] = None
|
||
292| last_active: Optional[str] = None
|
||
293|
|
||
294|
|
||
295|class MicrodaoBadge(BaseModel):
|
||
296| """MicroDAO badge for agent display"""
|
||
297| id: str
|
||
298| name: str
|
||
299| slug: Optional[str] = None
|
||
300| role: Optional[str] = None # orchestrator, member, etc.
|
||
301| is_public: bool = True
|
||
302| is_platform: bool = False
|
||
303| logo_url: Optional[str] = None
|
||
304| banner_url: Optional[str] = None
|
||
305|
|
||
306|
|
||
307|class AgentCrewInfo(BaseModel):
|
||
308| """Information about agent's CrewAI team"""
|
||
309| has_crew_team: bool
|
||
310| crew_team_key: Optional[str] = None
|
||
311| matrix_room_id: Optional[str] = None
|
||
312|
|
||
313|
|
||
314|class AgentSummary(BaseModel):
|
||
315| """Unified Agent summary for Agent Console and Citizens"""
|
||
316| id: str
|
||
317| slug: Optional[str] = None
|
||
318| display_name: str
|
||
319| title: Optional[str] = None # public_title
|
||
320| tagline: Optional[str] = None # public_tagline
|
||
321| kind: str = "assistant"
|
||
322| avatar_url: Optional[str] = None
|
||
323| status: str = "offline"
|
||
324|
|
||
325| # Node info
|
||
326| node_id: Optional[str] = None
|
||
327| node_label: Optional[str] = None # "НОДА1" / "НОДА2"
|
||
328| home_node: Optional[HomeNodeView] = None
|
||
329|
|
||
330| # Governance & DAIS (A1, A2)
|
||
331| gov_level: Optional[str] = None # personal, core_team, orchestrator, district_lead, city_governance
|
||
332| dais_identity_id: Optional[str] = None # DAIS identity reference
|
||
333|
|
||
334| # Visibility & roles
|
||
335| visibility_scope: str = "city" # global, microdao, private
|
||
336| is_listed_in_directory: bool = True
|
||
337| is_system: bool = False
|
||
338| is_public: bool = False
|
||
339| is_orchestrator: bool = False # Can create/manage microDAOs
|
||
340|
|
||
341| # MicroDAO (A3)
|
||
342| primary_microdao_id: Optional[str] = None
|
||
343| primary_microdao_name: Optional[str] = None
|
||
344| primary_microdao_slug: Optional[str] = None
|
||
345| home_microdao_id: Optional[str] = None # Owner microDAO
|
||
346| home_microdao_name: Optional[str] = None
|
||
347| home_microdao_slug: Optional[str] = None
|
||
348| district: Optional[str] = None
|
||
349| microdaos: List[MicrodaoBadge] = []
|
||
350| microdao_memberships: List[Dict[str, Any]] = [] # backward compatibility
|
||
351|
|
||
352| # Skills
|
||
353| public_skills: List[str] = []
|
||
354|
|
||
355| # CrewAI
|
||
356| crew_info: Optional[AgentCrewInfo] = None
|
||
357|
|
||
358| # Future: model bindings and usage stats
|
||
359| model_bindings: Optional[ModelBindings] = None
|
||
360| usage_stats: Optional[UsageStats] = None
|
||
361|
|
||
362|
|
||
363|class PublicCitizenSummary(BaseModel):
|
||
364| slug: str
|
||
365| display_name: str
|
||
366| public_title: Optional[str] = None
|
||
367| public_tagline: Optional[str] = None
|
||
368| avatar_url: Optional[str] = None
|
||
369| kind: Optional[str] = None
|
||
370| district: Optional[str] = None
|
||
371| primary_room_slug: Optional[str] = None
|
||
372| public_skills: List[str] = []
|
||
373| online_status: Optional[str] = "unknown"
|
||
374| status: Optional[str] = None # backward compatibility
|
||
375| # Home node info
|
||
376| home_node: Optional[HomeNodeView] = None
|
||
377| node_id: Optional[str] = None
|
||
378|
|
||
379| # TASK 037A: Alignment
|
||
380| home_microdao_slug: Optional[str] = None
|
||
381| home_microdao_name: Optional[str] = None
|
||
382| primary_city_room: Optional["CityRoomSummary"] = None
|
||
383|
|
||
384|
|
||
385|class PublicCitizenProfile(BaseModel):
|
||
386| slug: str
|
||
387| display_name: str
|
||
388| kind: Optional[str] = None
|
||
389| public_title: Optional[str] = None
|
||
390| public_tagline: Optional[str] = None
|
||
391| district: Optional[str] = None
|
||
392| avatar_url: Optional[str] = None
|
||
393| status: Optional[str] = None
|
||
394| node_id: Optional[str] = None
|
||
395| public_skills: List[str] = []
|
||
396| city_presence: Optional[CityPresenceView] = None
|
||
397| dais_public: Dict[str, Any]
|
||
398| interaction: Dict[str, Any]
|
||
399| metrics_public: Dict[str, Any]
|
||
400| admin_panel_url: Optional[str] = None
|
||
401| microdao: Optional[Dict[str, Any]] = None
|
||
402| # Home node info
|
||
403| home_node: Optional[HomeNodeView] = None
|
||
404|
|
||
405|
|
||
406|class CitizenInteractionInfo(BaseModel):
|
||
407| slug: str
|
||
408| display_name: str
|
||
409| primary_room_slug: Optional[str] = None
|
||
410| primary_room_id: Optional[str] = None
|
||
411| primary_room_name: Optional[str] = None
|
||
412| matrix_user_id: Optional[str] = None
|
||
413| district: Optional[str] = None
|
||
414| microdao_slug: Optional[str] = None
|
||
415| microdao_name: Optional[str] = None
|
||
416|
|
||
417|
|
||
418|class CitizenAskRequest(BaseModel):
|
||
419| question: str
|
||
420| context: Optional[str] = None
|
||
421|
|
||
422|
|
||
423|class CitizenAskResponse(BaseModel):
|
||
424| answer: str
|
||
425| agent_display_name: str
|
||
426| agent_id: str
|
||
427|
|
||
428|
|
||
429|# =============================================================================
|
||
430|# MicroDAO
|
||
431|# =============================================================================
|
||
432|
|
||
433|class MicrodaoCitizenView(BaseModel):
|
||
434| slug: str
|
||
435| display_name: str
|
||
436| public_title: Optional[str] = None
|
||
437| public_tagline: Optional[str] = None
|
||
438| avatar_url: Optional[str] = None
|
||
439| district: Optional[str] = None
|
||
440| primary_room_slug: Optional[str] = None
|
||
441|
|
||
442|
|
||
443|class MicrodaoSummary(BaseModel):
|
||
444| """MicroDAO summary for list view"""
|
||
445| id: str
|
||
446| slug: str
|
||
447| name: str
|
||
448| description: Optional[str] = None
|
||
449| district: Optional[str] = None
|
||
450|
|
||
451| # Visibility & type
|
||
452| is_public: bool = True
|
||
453| is_platform: bool = False # Is a platform/district
|
||
454| is_active: bool = True
|
||
455|
|
||
456| # Orchestrator
|
||
457| orchestrator_agent_id: Optional[str] = None
|
||
458| orchestrator_agent_name: Optional[str] = None
|
||
459|
|
||
460| # Hierarchy
|
||
461| parent_microdao_id: Optional[str] = None
|
||
462| parent_microdao_slug: Optional[str] = None
|
||
463|
|
||
464| # Stats
|
||
465| logo_url: Optional[str] = None
|
||
466| banner_url: Optional[str] = None
|
||
467| member_count: int = 0 # alias for agents_count
|
||
468| agents_count: int = 0 # backward compatibility
|
||
469| room_count: int = 0 # alias for rooms_count
|
||
470| rooms_count: int = 0 # backward compatibility
|
||
471| channels_count: int = 0
|
||
472|
|
||
473|
|
||
474|class MicrodaoChannelView(BaseModel):
|
||
475| """Channel/integration view for MicroDAO"""
|
||
476| kind: str # 'matrix' | 'telegram' | 'city_room' | 'crew'
|
||
477| ref_id: str
|
||
478| display_name: Optional[str] = None
|
||
479| is_primary: bool
|
||
480|
|
||
481|
|
||
482|class MicrodaoAgentView(BaseModel):
|
||
483| """Agent view within MicroDAO"""
|
||
484| agent_id: str
|
||
485| display_name: str
|
||
486| role: Optional[str] = None
|
||
487| is_core: bool
|
||
488|
|
||
489|
|
||
490|class CityRoomSummary(BaseModel):
|
||
491| """Summary of a city room for chat embedding and multi-room support"""
|
||
492| id: str
|
||
493| slug: str
|
||
494| name: str
|
||
495| matrix_room_id: Optional[str] = None
|
||
496| microdao_id: Optional[str] = None
|
||
497| microdao_slug: Optional[str] = None
|
||
498| room_role: Optional[str] = None # 'primary', 'lobby', 'team', 'research', 'security', 'governance', 'orchestrator_team'
|
||
499| is_public: bool = True
|
||
500| sort_order: int = 100
|
||
501| logo_url: Optional[str] = None
|
||
502| banner_url: Optional[str] = None
|
||
503|
|
||
504|
|
||
505|class MicrodaoRoomsList(BaseModel):
|
||
506| """List of rooms belonging to a MicroDAO"""
|
||
507| microdao_id: str
|
||
508| microdao_slug: str
|
||
509| rooms: List[CityRoomSummary] = []
|
||
510|
|
||
511|
|
||
512|class MicrodaoRoomUpdate(BaseModel):
|
||
513| """Update request for MicroDAO room settings"""
|
||
514| room_role: Optional[str] = None
|
||
515| is_public: Optional[bool] = None
|
||
516| sort_order: Optional[int] = None
|
||
517| set_primary: Optional[bool] = None # if true, mark as primary
|
||
518|
|
||
519|
|
||
520|class AttachExistingRoomRequest(BaseModel):
|
||
521| """Request to attach an existing city room to a MicroDAO"""
|
||
522| room_id: str
|
||
523| room_role: Optional[str] = None
|
||
524| is_public: bool = True
|
||
525| sort_order: int = 100
|
||
526|
|
||
527|
|
||
528|class MicrodaoDetail(BaseModel):
|
||
529| """Full MicroDAO detail view"""
|
||
530| id: str
|
||
531| slug: str
|
||
532| name: str
|
||
533| description: Optional[str] = None
|
||
534| district: Optional[str] = None
|
||
535|
|
||
536| # Visibility & type
|
||
537| is_public: bool = True
|
||
538| is_platform: bool = False
|
||
539| is_active: bool = True
|
||
540|
|
||
541| # Orchestrator
|
||
542| orchestrator_agent_id: Optional[str] = None
|
||
543| orchestrator_display_name: Optional[str] = None
|
||
544|
|
||
545| # Hierarchy
|
||
546| parent_microdao_id: Optional[str] = None
|
||
547| parent_microdao_slug: Optional[str] = None
|
||
548| child_microdaos: List["MicrodaoSummary"] = []
|
||
549|
|
||
550| # Content
|
||
551| logo_url: Optional[str] = None
|
||
552| banner_url: Optional[str] = None
|
||
553| agents: List[MicrodaoAgentView] = []
|
||
554| channels: List[MicrodaoChannelView] = []
|
||
555|
|
||
556| # Multi-room support
|
||
557| rooms: List[CityRoomSummary] = []
|
||
558| public_citizens: List[MicrodaoCitizenView] = []
|
||
559|
|
||
560| # Primary city room for chat
|
||
561| primary_city_room: Optional[CityRoomSummary] = None
|
||
562|
|
||
563|
|
||
564|class AgentMicrodaoMembership(BaseModel):
|
||
565| microdao_id: str
|
||
566| microdao_slug: str
|
||
567| microdao_name: str
|
||
568| logo_url: Optional[str] = None
|
||
569| role: Optional[str] = None
|
||
570| is_core: bool = False
|
||
571|
|
||
572|
|
||
573|class MicrodaoOption(BaseModel):
|
||
574| id: str
|
||
575| slug: str
|
||
576| name: str
|
||
577| district: Optional[str] = None
|
||
578| is_active: bool = True
|
||
579|
|
||
580|
|
||
581|# =============================================================================
|
||
582|# Visibility Updates (Task 029)
|
||
583|# =============================================================================
|
||
584|
|
||
585|class AgentVisibilityUpdate(BaseModel):
|
||
586| """Update agent visibility settings"""
|
||
587| is_public: bool
|
||
588| visibility_scope: Optional[str] = None # 'global' | 'microdao' | 'private'
|
||
589|
|
||
590|
|
||
591|class MicrodaoVisibilityUpdate(BaseModel):
|
||
592| """Update MicroDAO visibility settings"""
|
||
593| is_public: bool
|
||
594| is_platform: Optional[bool] = None # Upgrade to platform/district
|
||
595|
|
||
596|
|
||
597|class MicrodaoCreateRequest(BaseModel):
|
||
598| """Request to create MicroDAO from agent (orchestrator flow)"""
|
||
599| name: str
|
||
600| slug: str
|
||
601| description: Optional[str] = None
|
||
602| make_platform: bool = False # If true -> is_platform = true
|
||
603| is_public: bool = True
|
||
604| parent_microdao_id: Optional[str] = None
|
||
605|
|
||
606|
|
||
607|class SwapperModel(BaseModel):
|
||
608| """Model info from Swapper service"""
|
||
609| name: str
|
||
610| loaded: bool
|
||
611| type: Optional[str] = None
|
||
612| vram_gb: Optional[float] = None
|
||
613|
|
||
614|
|
||
615|class NodeSwapperDetail(BaseModel):
|
||
616| """Detailed Swapper info for Node Cabinet"""
|
||
617| node_id: str
|
||
618| healthy: bool
|
||
619| models_loaded: int
|
||
620| models_total: int
|
||
621| models: List[SwapperModel] = []
|