omni-cortex 1.12.0__py3-none-any.whl → 1.13.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (28) hide show
  1. omni_cortex-1.13.0.data/data/share/omni-cortex/dashboard/backend/chat_service.py +572 -0
  2. {omni_cortex-1.12.0.data → omni_cortex-1.13.0.data}/data/share/omni-cortex/dashboard/backend/database.py +1653 -1094
  3. {omni_cortex-1.12.0.data → omni_cortex-1.13.0.data}/data/share/omni-cortex/dashboard/backend/main.py +1681 -1381
  4. {omni_cortex-1.12.0.data → omni_cortex-1.13.0.data}/data/share/omni-cortex/dashboard/backend/models.py +400 -285
  5. {omni_cortex-1.12.0.dist-info → omni_cortex-1.13.0.dist-info}/METADATA +1 -1
  6. omni_cortex-1.13.0.dist-info/RECORD +26 -0
  7. omni_cortex-1.12.0.data/data/share/omni-cortex/dashboard/backend/chat_service.py +0 -317
  8. omni_cortex-1.12.0.dist-info/RECORD +0 -26
  9. {omni_cortex-1.12.0.data → omni_cortex-1.13.0.data}/data/share/omni-cortex/dashboard/backend/.env.example +0 -0
  10. {omni_cortex-1.12.0.data → omni_cortex-1.13.0.data}/data/share/omni-cortex/dashboard/backend/backfill_summaries.py +0 -0
  11. {omni_cortex-1.12.0.data → omni_cortex-1.13.0.data}/data/share/omni-cortex/dashboard/backend/image_service.py +0 -0
  12. {omni_cortex-1.12.0.data → omni_cortex-1.13.0.data}/data/share/omni-cortex/dashboard/backend/logging_config.py +0 -0
  13. {omni_cortex-1.12.0.data → omni_cortex-1.13.0.data}/data/share/omni-cortex/dashboard/backend/project_config.py +0 -0
  14. {omni_cortex-1.12.0.data → omni_cortex-1.13.0.data}/data/share/omni-cortex/dashboard/backend/project_scanner.py +0 -0
  15. {omni_cortex-1.12.0.data → omni_cortex-1.13.0.data}/data/share/omni-cortex/dashboard/backend/prompt_security.py +0 -0
  16. {omni_cortex-1.12.0.data → omni_cortex-1.13.0.data}/data/share/omni-cortex/dashboard/backend/pyproject.toml +0 -0
  17. {omni_cortex-1.12.0.data → omni_cortex-1.13.0.data}/data/share/omni-cortex/dashboard/backend/security.py +0 -0
  18. {omni_cortex-1.12.0.data → omni_cortex-1.13.0.data}/data/share/omni-cortex/dashboard/backend/uv.lock +0 -0
  19. {omni_cortex-1.12.0.data → omni_cortex-1.13.0.data}/data/share/omni-cortex/dashboard/backend/websocket_manager.py +0 -0
  20. {omni_cortex-1.12.0.data → omni_cortex-1.13.0.data}/data/share/omni-cortex/hooks/post_tool_use.py +0 -0
  21. {omni_cortex-1.12.0.data → omni_cortex-1.13.0.data}/data/share/omni-cortex/hooks/pre_tool_use.py +0 -0
  22. {omni_cortex-1.12.0.data → omni_cortex-1.13.0.data}/data/share/omni-cortex/hooks/session_utils.py +0 -0
  23. {omni_cortex-1.12.0.data → omni_cortex-1.13.0.data}/data/share/omni-cortex/hooks/stop.py +0 -0
  24. {omni_cortex-1.12.0.data → omni_cortex-1.13.0.data}/data/share/omni-cortex/hooks/subagent_stop.py +0 -0
  25. {omni_cortex-1.12.0.data → omni_cortex-1.13.0.data}/data/share/omni-cortex/hooks/user_prompt.py +0 -0
  26. {omni_cortex-1.12.0.dist-info → omni_cortex-1.13.0.dist-info}/WHEEL +0 -0
  27. {omni_cortex-1.12.0.dist-info → omni_cortex-1.13.0.dist-info}/entry_points.txt +0 -0
  28. {omni_cortex-1.12.0.dist-info → omni_cortex-1.13.0.dist-info}/licenses/LICENSE +0 -0
@@ -1,285 +1,400 @@
1
- """Pydantic models for the dashboard API."""
2
-
3
- from datetime import datetime
4
- from typing import Optional
5
-
6
- from pydantic import BaseModel, Field
7
-
8
-
9
- class ProjectInfo(BaseModel):
10
- """Information about a project with omni-cortex database."""
11
-
12
- name: str
13
- path: str
14
- db_path: str
15
- last_modified: Optional[datetime] = None
16
- memory_count: int = 0
17
- is_global: bool = False
18
- is_favorite: bool = False
19
- is_registered: bool = False
20
- display_name: Optional[str] = None
21
-
22
-
23
- class ScanDirectory(BaseModel):
24
- """A directory being scanned for projects."""
25
-
26
- path: str
27
- project_count: int = 0
28
-
29
-
30
- class ProjectRegistration(BaseModel):
31
- """Request to register a project."""
32
-
33
- path: str
34
- display_name: Optional[str] = None
35
-
36
-
37
- class ProjectConfigResponse(BaseModel):
38
- """Response with project configuration."""
39
-
40
- scan_directories: list[str]
41
- registered_count: int
42
- favorites_count: int
43
-
44
-
45
- class Memory(BaseModel):
46
- """Memory record from the database."""
47
-
48
- id: str
49
- content: str
50
- context: Optional[str] = None
51
- memory_type: str = Field(default="other", validation_alias="type")
52
- status: str = "fresh"
53
- importance_score: int = 50
54
- access_count: int = 0
55
- created_at: datetime
56
- last_accessed: Optional[datetime] = None
57
- tags: list[str] = []
58
-
59
- model_config = {"populate_by_name": True}
60
-
61
-
62
- class MemoryStats(BaseModel):
63
- """Statistics about memories in a database."""
64
-
65
- total_count: int
66
- by_type: dict[str, int]
67
- by_status: dict[str, int]
68
- avg_importance: float
69
- total_access_count: int
70
- tags: list[dict[str, int | str]]
71
-
72
-
73
- class FilterParams(BaseModel):
74
- """Query filter parameters."""
75
-
76
- memory_type: Optional[str] = None
77
- status: Optional[str] = None
78
- tags: Optional[list[str]] = None
79
- search: Optional[str] = None
80
- min_importance: Optional[int] = None
81
- max_importance: Optional[int] = None
82
- sort_by: str = "last_accessed"
83
- sort_order: str = "desc"
84
- limit: int = 50
85
- offset: int = 0
86
-
87
-
88
- class AggregateMemoryRequest(BaseModel):
89
- """Request for aggregate memory data across projects."""
90
-
91
- projects: list[str] = Field(..., description="List of project db paths")
92
- filters: Optional[FilterParams] = None
93
-
94
-
95
- class AggregateStatsRequest(BaseModel):
96
- """Request for aggregate statistics."""
97
-
98
- projects: list[str] = Field(..., description="List of project db paths")
99
-
100
-
101
- class AggregateStatsResponse(BaseModel):
102
- """Aggregate statistics across multiple projects."""
103
-
104
- total_count: int
105
- total_access_count: int
106
- avg_importance: float
107
- by_type: dict[str, int]
108
- by_status: dict[str, int]
109
- project_count: int
110
-
111
-
112
- class AggregateChatRequest(BaseModel):
113
- """Request for chat across multiple projects."""
114
-
115
- projects: list[str] = Field(..., description="List of project db paths")
116
- question: str = Field(..., min_length=1, max_length=2000)
117
- max_memories_per_project: int = Field(default=5, ge=1, le=20)
118
-
119
-
120
- class Activity(BaseModel):
121
- """Activity log record."""
122
-
123
- id: str
124
- session_id: Optional[str] = None
125
- event_type: str
126
- tool_name: Optional[str] = None
127
- tool_input: Optional[str] = None
128
- tool_output: Optional[str] = None
129
- success: bool = True
130
- error_message: Optional[str] = None
131
- duration_ms: Optional[int] = None
132
- file_path: Optional[str] = None
133
- timestamp: datetime
134
- # Command analytics fields
135
- command_name: Optional[str] = None
136
- command_scope: Optional[str] = None
137
- mcp_server: Optional[str] = None
138
- skill_name: Optional[str] = None
139
- # Natural language summary fields
140
- summary: Optional[str] = None
141
- summary_detail: Optional[str] = None
142
-
143
-
144
- class Session(BaseModel):
145
- """Session record."""
146
-
147
- id: str
148
- project_path: str
149
- started_at: datetime
150
- ended_at: Optional[datetime] = None
151
- summary: Optional[str] = None
152
- activity_count: int = 0
153
-
154
-
155
- class TimelineEntry(BaseModel):
156
- """Entry in the timeline view."""
157
-
158
- timestamp: datetime
159
- entry_type: str # "memory" or "activity"
160
- data: dict
161
-
162
-
163
- class MemoryCreateRequest(BaseModel):
164
- """Create request for a new memory."""
165
-
166
- content: str = Field(..., min_length=1, max_length=50000)
167
- memory_type: str = Field(default="general")
168
- context: Optional[str] = None
169
- importance_score: int = Field(default=50, ge=1, le=100)
170
- tags: list[str] = Field(default_factory=list)
171
-
172
-
173
- class MemoryUpdate(BaseModel):
174
- """Update request for a memory."""
175
-
176
- content: Optional[str] = None
177
- context: Optional[str] = None
178
- memory_type: Optional[str] = Field(None, validation_alias="type")
179
- status: Optional[str] = None
180
- importance_score: Optional[int] = Field(None, ge=1, le=100)
181
- tags: Optional[list[str]] = None
182
-
183
- model_config = {"populate_by_name": True}
184
-
185
-
186
- class WSEvent(BaseModel):
187
- """WebSocket event message."""
188
-
189
- event_type: str
190
- data: dict
191
- timestamp: datetime = Field(default_factory=datetime.now)
192
-
193
-
194
- class ChatRequest(BaseModel):
195
- """Request for the chat endpoint."""
196
-
197
- question: str = Field(..., min_length=1, max_length=2000)
198
- max_memories: int = Field(default=10, ge=1, le=50)
199
-
200
-
201
- class ChatSource(BaseModel):
202
- """Source memory reference in chat response."""
203
-
204
- id: str
205
- type: str
206
- content_preview: str
207
- tags: list[str]
208
- project_path: Optional[str] = None
209
- project_name: Optional[str] = None
210
-
211
-
212
- class ChatResponse(BaseModel):
213
- """Response from the chat endpoint."""
214
-
215
- answer: str
216
- sources: list[ChatSource]
217
- error: Optional[str] = None
218
-
219
-
220
- class ConversationMessage(BaseModel):
221
- """A message in a conversation."""
222
-
223
- role: str # 'user' or 'assistant'
224
- content: str
225
- timestamp: str
226
-
227
-
228
- class ConversationSaveRequest(BaseModel):
229
- """Request to save a conversation as memory."""
230
-
231
- messages: list[ConversationMessage]
232
- referenced_memory_ids: Optional[list[str]] = None
233
- importance: Optional[int] = Field(default=60, ge=1, le=100)
234
-
235
-
236
- class ConversationSaveResponse(BaseModel):
237
- """Response after saving a conversation."""
238
-
239
- memory_id: str
240
- summary: str
241
-
242
-
243
- # --- Image Generation Models ---
244
-
245
-
246
- class SingleImageRequestModel(BaseModel):
247
- """Request for a single image in a batch."""
248
- preset: str = "custom" # Maps to ImagePreset enum
249
- custom_prompt: str = ""
250
- aspect_ratio: str = "16:9"
251
- image_size: str = "2K"
252
-
253
-
254
- class BatchImageGenerationRequest(BaseModel):
255
- """Request for generating multiple images."""
256
- images: list[SingleImageRequestModel] # 1, 2, or 4 images
257
- memory_ids: list[str] = []
258
- chat_messages: list[dict] = [] # Recent chat for context
259
- use_search_grounding: bool = False
260
-
261
-
262
- class ImageRefineRequest(BaseModel):
263
- """Request for refining an existing image."""
264
- image_id: str
265
- refinement_prompt: str
266
- aspect_ratio: Optional[str] = None
267
- image_size: Optional[str] = None
268
-
269
-
270
- class SingleImageResponseModel(BaseModel):
271
- """Response for a single generated image."""
272
- success: bool
273
- image_data: Optional[str] = None # Base64 encoded
274
- text_response: Optional[str] = None
275
- thought_signature: Optional[str] = None
276
- image_id: Optional[str] = None
277
- error: Optional[str] = None
278
- index: int = 0
279
-
280
-
281
- class BatchImageGenerationResponse(BaseModel):
282
- """Response for batch image generation."""
283
- success: bool
284
- images: list[SingleImageResponseModel] = []
285
- errors: list[str] = []
1
+ """Pydantic models for the dashboard API."""
2
+
3
+ from datetime import datetime
4
+ from typing import Optional
5
+
6
+ from pydantic import BaseModel, Field
7
+
8
+
9
+ class ProjectInfo(BaseModel):
10
+ """Information about a project with omni-cortex database."""
11
+
12
+ name: str
13
+ path: str
14
+ db_path: str
15
+ last_modified: Optional[datetime] = None
16
+ memory_count: int = 0
17
+ is_global: bool = False
18
+ is_favorite: bool = False
19
+ is_registered: bool = False
20
+ display_name: Optional[str] = None
21
+
22
+
23
+ class ScanDirectory(BaseModel):
24
+ """A directory being scanned for projects."""
25
+
26
+ path: str
27
+ project_count: int = 0
28
+
29
+
30
+ class ProjectRegistration(BaseModel):
31
+ """Request to register a project."""
32
+
33
+ path: str
34
+ display_name: Optional[str] = None
35
+
36
+
37
+ class ProjectConfigResponse(BaseModel):
38
+ """Response with project configuration."""
39
+
40
+ scan_directories: list[str]
41
+ registered_count: int
42
+ favorites_count: int
43
+
44
+
45
+ class Memory(BaseModel):
46
+ """Memory record from the database."""
47
+
48
+ id: str
49
+ content: str
50
+ context: Optional[str] = None
51
+ memory_type: str = Field(default="other", validation_alias="type")
52
+ status: str = "fresh"
53
+ importance_score: int = 50
54
+ access_count: int = 0
55
+ created_at: datetime
56
+ last_accessed: Optional[datetime] = None
57
+ tags: list[str] = []
58
+
59
+ model_config = {"populate_by_name": True}
60
+
61
+
62
+ class MemoryStats(BaseModel):
63
+ """Statistics about memories in a database."""
64
+
65
+ total_count: int
66
+ by_type: dict[str, int]
67
+ by_status: dict[str, int]
68
+ avg_importance: float
69
+ total_access_count: int
70
+ tags: list[dict[str, int | str]]
71
+
72
+
73
+ class FilterParams(BaseModel):
74
+ """Query filter parameters."""
75
+
76
+ memory_type: Optional[str] = None
77
+ status: Optional[str] = None
78
+ tags: Optional[list[str]] = None
79
+ search: Optional[str] = None
80
+ min_importance: Optional[int] = None
81
+ max_importance: Optional[int] = None
82
+ sort_by: str = "last_accessed"
83
+ sort_order: str = "desc"
84
+ limit: int = 50
85
+ offset: int = 0
86
+
87
+
88
+ class AggregateMemoryRequest(BaseModel):
89
+ """Request for aggregate memory data across projects."""
90
+
91
+ projects: list[str] = Field(..., description="List of project db paths")
92
+ filters: Optional[FilterParams] = None
93
+
94
+
95
+ class AggregateStatsRequest(BaseModel):
96
+ """Request for aggregate statistics."""
97
+
98
+ projects: list[str] = Field(..., description="List of project db paths")
99
+
100
+
101
+ class AggregateStatsResponse(BaseModel):
102
+ """Aggregate statistics across multiple projects."""
103
+
104
+ total_count: int
105
+ total_access_count: int
106
+ avg_importance: float
107
+ by_type: dict[str, int]
108
+ by_status: dict[str, int]
109
+ project_count: int
110
+
111
+
112
+ class AggregateChatRequest(BaseModel):
113
+ """Request for chat across multiple projects."""
114
+
115
+ projects: list[str] = Field(..., description="List of project db paths")
116
+ question: str = Field(..., min_length=1, max_length=2000)
117
+ max_memories_per_project: int = Field(default=5, ge=1, le=20)
118
+
119
+
120
+ class Activity(BaseModel):
121
+ """Activity log record."""
122
+
123
+ id: str
124
+ session_id: Optional[str] = None
125
+ event_type: str
126
+ tool_name: Optional[str] = None
127
+ tool_input: Optional[str] = None
128
+ tool_output: Optional[str] = None
129
+ success: bool = True
130
+ error_message: Optional[str] = None
131
+ duration_ms: Optional[int] = None
132
+ file_path: Optional[str] = None
133
+ timestamp: datetime
134
+ # Command analytics fields
135
+ command_name: Optional[str] = None
136
+ command_scope: Optional[str] = None
137
+ mcp_server: Optional[str] = None
138
+ skill_name: Optional[str] = None
139
+ # Natural language summary fields
140
+ summary: Optional[str] = None
141
+ summary_detail: Optional[str] = None
142
+
143
+
144
+ class Session(BaseModel):
145
+ """Session record."""
146
+
147
+ id: str
148
+ project_path: str
149
+ started_at: datetime
150
+ ended_at: Optional[datetime] = None
151
+ summary: Optional[str] = None
152
+ activity_count: int = 0
153
+
154
+
155
+ class TimelineEntry(BaseModel):
156
+ """Entry in the timeline view."""
157
+
158
+ timestamp: datetime
159
+ entry_type: str # "memory" or "activity"
160
+ data: dict
161
+
162
+
163
+ class MemoryCreateRequest(BaseModel):
164
+ """Create request for a new memory."""
165
+
166
+ content: str = Field(..., min_length=1, max_length=50000)
167
+ memory_type: str = Field(default="general")
168
+ context: Optional[str] = None
169
+ importance_score: int = Field(default=50, ge=1, le=100)
170
+ tags: list[str] = Field(default_factory=list)
171
+
172
+
173
+ class MemoryUpdate(BaseModel):
174
+ """Update request for a memory."""
175
+
176
+ content: Optional[str] = None
177
+ context: Optional[str] = None
178
+ memory_type: Optional[str] = Field(None, validation_alias="type")
179
+ status: Optional[str] = None
180
+ importance_score: Optional[int] = Field(None, ge=1, le=100)
181
+ tags: Optional[list[str]] = None
182
+
183
+ model_config = {"populate_by_name": True}
184
+
185
+
186
+ class WSEvent(BaseModel):
187
+ """WebSocket event message."""
188
+
189
+ event_type: str
190
+ data: dict
191
+ timestamp: datetime = Field(default_factory=datetime.now)
192
+
193
+
194
+ class ChatRequest(BaseModel):
195
+ """Request for the chat endpoint."""
196
+
197
+ question: str = Field(..., min_length=1, max_length=2000)
198
+ max_memories: int = Field(default=10, ge=1, le=50)
199
+ use_style: bool = Field(default=False)
200
+
201
+
202
+ class ChatSource(BaseModel):
203
+ """Source memory reference in chat response."""
204
+
205
+ id: str
206
+ type: str
207
+ content_preview: str
208
+ tags: list[str]
209
+ project_path: Optional[str] = None
210
+ project_name: Optional[str] = None
211
+
212
+
213
+ class ChatResponse(BaseModel):
214
+ """Response from the chat endpoint."""
215
+
216
+ answer: str
217
+ sources: list[ChatSource]
218
+ error: Optional[str] = None
219
+
220
+
221
+ class ConversationMessage(BaseModel):
222
+ """A message in a conversation."""
223
+
224
+ role: str # 'user' or 'assistant'
225
+ content: str
226
+ timestamp: str
227
+
228
+
229
+ class ConversationSaveRequest(BaseModel):
230
+ """Request to save a conversation as memory."""
231
+
232
+ messages: list[ConversationMessage]
233
+ referenced_memory_ids: Optional[list[str]] = None
234
+ importance: Optional[int] = Field(default=60, ge=1, le=100)
235
+
236
+
237
+ class ConversationSaveResponse(BaseModel):
238
+ """Response after saving a conversation."""
239
+
240
+ memory_id: str
241
+ summary: str
242
+
243
+
244
+ # --- Image Generation Models ---
245
+
246
+
247
+ class SingleImageRequestModel(BaseModel):
248
+ """Request for a single image in a batch."""
249
+ preset: str = "custom" # Maps to ImagePreset enum
250
+ custom_prompt: str = ""
251
+ aspect_ratio: str = "16:9"
252
+ image_size: str = "2K"
253
+
254
+
255
+ class BatchImageGenerationRequest(BaseModel):
256
+ """Request for generating multiple images."""
257
+ images: list[SingleImageRequestModel] # 1, 2, or 4 images
258
+ memory_ids: list[str] = []
259
+ chat_messages: list[dict] = [] # Recent chat for context
260
+ use_search_grounding: bool = False
261
+
262
+
263
+ class ImageRefineRequest(BaseModel):
264
+ """Request for refining an existing image."""
265
+ image_id: str
266
+ refinement_prompt: str
267
+ aspect_ratio: Optional[str] = None
268
+ image_size: Optional[str] = None
269
+
270
+
271
+ class SingleImageResponseModel(BaseModel):
272
+ """Response for a single generated image."""
273
+ success: bool
274
+ image_data: Optional[str] = None # Base64 encoded
275
+ text_response: Optional[str] = None
276
+ thought_signature: Optional[str] = None
277
+ image_id: Optional[str] = None
278
+ error: Optional[str] = None
279
+ index: int = 0
280
+
281
+
282
+ class BatchImageGenerationResponse(BaseModel):
283
+ """Response for batch image generation."""
284
+ success: bool
285
+ images: list[SingleImageResponseModel] = []
286
+ errors: list[str] = []
287
+
288
+
289
+ # --- User Messages & Style Profile Models ---
290
+
291
+
292
+ class UserMessage(BaseModel):
293
+ """User message record from the database."""
294
+
295
+ id: str
296
+ session_id: Optional[str] = None
297
+ timestamp: Optional[str] = None # Backward compatibility
298
+ created_at: Optional[str] = None # Frontend expects created_at
299
+ content: str
300
+ word_count: Optional[int] = None
301
+ char_count: Optional[int] = None
302
+ line_count: Optional[int] = None
303
+ has_code_blocks: bool = False
304
+ has_questions: bool = False
305
+ has_commands: bool = False
306
+ tone: Optional[str] = None # Primary tone for frontend
307
+ tone_indicators: list[str] = []
308
+ project_path: Optional[str] = None
309
+
310
+
311
+ class UserMessageFilters(BaseModel):
312
+ """Query filter parameters for user messages."""
313
+
314
+ session_id: Optional[str] = None
315
+ search: Optional[str] = None
316
+ has_code_blocks: Optional[bool] = None
317
+ has_questions: Optional[bool] = None
318
+ has_commands: Optional[bool] = None
319
+ tone_filter: Optional[str] = None
320
+ sort_by: str = "timestamp"
321
+ sort_order: str = "desc"
322
+ limit: int = Field(default=50, ge=1, le=500)
323
+ offset: int = Field(default=0, ge=0)
324
+
325
+
326
+ class UserMessagesResponse(BaseModel):
327
+ """Response containing user messages with pagination info."""
328
+
329
+ messages: list[UserMessage]
330
+ total_count: int
331
+ limit: int
332
+ offset: int
333
+ has_more: bool = False # Whether more results are available
334
+
335
+
336
+ class StyleSample(BaseModel):
337
+ """A sample message for style preview."""
338
+
339
+ id: str
340
+ timestamp: str
341
+ content_preview: str
342
+ word_count: Optional[int] = None
343
+ has_code_blocks: bool = False
344
+ has_questions: bool = False
345
+ tone_indicators: list[str] = []
346
+
347
+
348
+ class StyleProfile(BaseModel):
349
+ """User style profile for aggregated style analysis."""
350
+
351
+ id: str
352
+ project_path: Optional[str] = None
353
+ total_messages: int = 0
354
+ avg_word_count: Optional[float] = None
355
+ avg_char_count: Optional[float] = None
356
+ common_phrases: Optional[list[str]] = None
357
+ vocabulary_richness: Optional[float] = None
358
+ formality_score: Optional[float] = None
359
+ question_frequency: Optional[float] = None
360
+ command_frequency: Optional[float] = None
361
+ code_block_frequency: Optional[float] = None
362
+ punctuation_style: Optional[dict] = None
363
+ greeting_patterns: Optional[list[str]] = None
364
+ instruction_style: Optional[dict] = None
365
+ sample_messages: Optional[list[str]] = None
366
+ created_at: str
367
+ updated_at: str
368
+
369
+
370
+ class BulkDeleteRequest(BaseModel):
371
+ """Request body for bulk delete operations."""
372
+
373
+ message_ids: list[str] = Field(..., min_length=1, max_length=100)
374
+
375
+
376
+ # --- Response Composer Models ---
377
+
378
+
379
+ class ComposeRequest(BaseModel):
380
+ """Request for composing a response in user's style."""
381
+
382
+ incoming_message: str = Field(..., min_length=1, max_length=5000)
383
+ context_type: str = Field(default="general") # skool_post, dm, email, comment, general
384
+ template: Optional[str] = None # answer, guide, redirect, acknowledge
385
+ tone_level: int = Field(default=50, ge=0, le=100) # 0=casual, 100=professional
386
+ include_memories: bool = Field(default=True)
387
+
388
+
389
+ class ComposeResponse(BaseModel):
390
+ """Response from compose endpoint."""
391
+
392
+ id: str
393
+ response: str
394
+ sources: list[ChatSource]
395
+ style_applied: bool
396
+ tone_level: int
397
+ template_used: Optional[str]
398
+ incoming_message: str
399
+ context_type: str
400
+ created_at: str