omni-cortex 1.0.4__py3-none-any.whl → 1.3.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.
- omni_cortex-1.3.0.data/data/share/omni-cortex/dashboard/backend/chat_service.py +308 -0
- {omni_cortex-1.0.4.data → omni_cortex-1.3.0.data}/data/share/omni-cortex/dashboard/backend/database.py +286 -0
- omni_cortex-1.3.0.data/data/share/omni-cortex/dashboard/backend/image_service.py +543 -0
- omni_cortex-1.3.0.data/data/share/omni-cortex/dashboard/backend/logging_config.py +92 -0
- {omni_cortex-1.0.4.data → omni_cortex-1.3.0.data}/data/share/omni-cortex/dashboard/backend/main.py +385 -42
- {omni_cortex-1.0.4.data → omni_cortex-1.3.0.data}/data/share/omni-cortex/dashboard/backend/models.py +93 -0
- omni_cortex-1.3.0.data/data/share/omni-cortex/dashboard/backend/project_config.py +170 -0
- {omni_cortex-1.0.4.data → omni_cortex-1.3.0.data}/data/share/omni-cortex/dashboard/backend/project_scanner.py +45 -22
- {omni_cortex-1.0.4.data → omni_cortex-1.3.0.data}/data/share/omni-cortex/dashboard/backend/uv.lock +414 -1
- {omni_cortex-1.0.4.dist-info → omni_cortex-1.3.0.dist-info}/METADATA +26 -2
- omni_cortex-1.3.0.dist-info/RECORD +20 -0
- omni_cortex-1.0.4.data/data/share/omni-cortex/dashboard/backend/chat_service.py +0 -140
- omni_cortex-1.0.4.dist-info/RECORD +0 -17
- {omni_cortex-1.0.4.data → omni_cortex-1.3.0.data}/data/share/omni-cortex/dashboard/backend/pyproject.toml +0 -0
- {omni_cortex-1.0.4.data → omni_cortex-1.3.0.data}/data/share/omni-cortex/dashboard/backend/websocket_manager.py +0 -0
- {omni_cortex-1.0.4.data → omni_cortex-1.3.0.data}/data/share/omni-cortex/hooks/post_tool_use.py +0 -0
- {omni_cortex-1.0.4.data → omni_cortex-1.3.0.data}/data/share/omni-cortex/hooks/pre_tool_use.py +0 -0
- {omni_cortex-1.0.4.data → omni_cortex-1.3.0.data}/data/share/omni-cortex/hooks/stop.py +0 -0
- {omni_cortex-1.0.4.data → omni_cortex-1.3.0.data}/data/share/omni-cortex/hooks/subagent_stop.py +0 -0
- {omni_cortex-1.0.4.dist-info → omni_cortex-1.3.0.dist-info}/WHEEL +0 -0
- {omni_cortex-1.0.4.dist-info → omni_cortex-1.3.0.dist-info}/entry_points.txt +0 -0
- {omni_cortex-1.0.4.dist-info → omni_cortex-1.3.0.dist-info}/licenses/LICENSE +0 -0
{omni_cortex-1.0.4.data → omni_cortex-1.3.0.data}/data/share/omni-cortex/dashboard/backend/main.py
RENAMED
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
|
|
4
4
|
import asyncio
|
|
5
5
|
import json
|
|
6
|
+
import traceback
|
|
6
7
|
from contextlib import asynccontextmanager
|
|
7
8
|
from datetime import datetime
|
|
8
9
|
from pathlib import Path
|
|
@@ -20,8 +21,11 @@ from database import (
|
|
|
20
21
|
bulk_update_memory_status,
|
|
21
22
|
delete_memory,
|
|
22
23
|
get_activities,
|
|
24
|
+
get_activity_detail,
|
|
23
25
|
get_activity_heatmap,
|
|
24
26
|
get_all_tags,
|
|
27
|
+
get_command_usage,
|
|
28
|
+
get_mcp_usage,
|
|
25
29
|
get_memories,
|
|
26
30
|
get_memories_needing_review,
|
|
27
31
|
get_memory_by_id,
|
|
@@ -31,16 +35,41 @@ from database import (
|
|
|
31
35
|
get_relationship_graph,
|
|
32
36
|
get_relationships,
|
|
33
37
|
get_sessions,
|
|
38
|
+
get_skill_usage,
|
|
34
39
|
get_timeline,
|
|
35
40
|
get_tool_usage,
|
|
36
41
|
get_type_distribution,
|
|
37
42
|
search_memories,
|
|
38
43
|
update_memory,
|
|
39
44
|
)
|
|
40
|
-
from
|
|
45
|
+
from logging_config import log_success, log_error
|
|
46
|
+
from models import (
|
|
47
|
+
ChatRequest,
|
|
48
|
+
ChatResponse,
|
|
49
|
+
ConversationSaveRequest,
|
|
50
|
+
ConversationSaveResponse,
|
|
51
|
+
FilterParams,
|
|
52
|
+
MemoryUpdate,
|
|
53
|
+
ProjectInfo,
|
|
54
|
+
ProjectRegistration,
|
|
55
|
+
BatchImageGenerationRequest,
|
|
56
|
+
BatchImageGenerationResponse,
|
|
57
|
+
ImageRefineRequest,
|
|
58
|
+
SingleImageRequestModel,
|
|
59
|
+
SingleImageResponseModel,
|
|
60
|
+
)
|
|
61
|
+
from project_config import (
|
|
62
|
+
load_config,
|
|
63
|
+
add_registered_project,
|
|
64
|
+
remove_registered_project,
|
|
65
|
+
toggle_favorite,
|
|
66
|
+
add_scan_directory,
|
|
67
|
+
remove_scan_directory,
|
|
68
|
+
)
|
|
41
69
|
from project_scanner import scan_projects
|
|
42
70
|
from websocket_manager import manager
|
|
43
71
|
import chat_service
|
|
72
|
+
from image_service import image_service, ImagePreset, SingleImageRequest
|
|
44
73
|
|
|
45
74
|
|
|
46
75
|
class DatabaseChangeHandler(FileSystemEventHandler):
|
|
@@ -145,6 +174,70 @@ async def list_projects():
|
|
|
145
174
|
return scan_projects()
|
|
146
175
|
|
|
147
176
|
|
|
177
|
+
# --- Project Management Endpoints ---
|
|
178
|
+
|
|
179
|
+
|
|
180
|
+
@app.get("/api/projects/config")
|
|
181
|
+
async def get_project_config():
|
|
182
|
+
"""Get project configuration (scan dirs, counts)."""
|
|
183
|
+
config = load_config()
|
|
184
|
+
return {
|
|
185
|
+
"scan_directories": config.scan_directories,
|
|
186
|
+
"registered_count": len(config.registered_projects),
|
|
187
|
+
"favorites_count": len(config.favorites),
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
|
|
191
|
+
@app.post("/api/projects/register")
|
|
192
|
+
async def register_project(body: ProjectRegistration):
|
|
193
|
+
"""Manually register a project by path."""
|
|
194
|
+
success = add_registered_project(body.path, body.display_name)
|
|
195
|
+
if not success:
|
|
196
|
+
raise HTTPException(400, "Invalid path or already registered")
|
|
197
|
+
return {"success": True}
|
|
198
|
+
|
|
199
|
+
|
|
200
|
+
@app.delete("/api/projects/register")
|
|
201
|
+
async def unregister_project(path: str = Query(..., description="Project path to unregister")):
|
|
202
|
+
"""Remove a registered project."""
|
|
203
|
+
success = remove_registered_project(path)
|
|
204
|
+
if not success:
|
|
205
|
+
raise HTTPException(404, "Project not found")
|
|
206
|
+
return {"success": True}
|
|
207
|
+
|
|
208
|
+
|
|
209
|
+
@app.post("/api/projects/favorite")
|
|
210
|
+
async def toggle_project_favorite(path: str = Query(..., description="Project path to toggle favorite")):
|
|
211
|
+
"""Toggle favorite status for a project."""
|
|
212
|
+
is_favorite = toggle_favorite(path)
|
|
213
|
+
return {"is_favorite": is_favorite}
|
|
214
|
+
|
|
215
|
+
|
|
216
|
+
@app.post("/api/projects/scan-directories")
|
|
217
|
+
async def add_scan_dir(directory: str = Query(..., description="Directory path to add")):
|
|
218
|
+
"""Add a directory to auto-scan list."""
|
|
219
|
+
success = add_scan_directory(directory)
|
|
220
|
+
if not success:
|
|
221
|
+
raise HTTPException(400, "Invalid directory or already added")
|
|
222
|
+
return {"success": True}
|
|
223
|
+
|
|
224
|
+
|
|
225
|
+
@app.delete("/api/projects/scan-directories")
|
|
226
|
+
async def remove_scan_dir(directory: str = Query(..., description="Directory path to remove")):
|
|
227
|
+
"""Remove a directory from auto-scan list."""
|
|
228
|
+
success = remove_scan_directory(directory)
|
|
229
|
+
if not success:
|
|
230
|
+
raise HTTPException(404, "Directory not found")
|
|
231
|
+
return {"success": True}
|
|
232
|
+
|
|
233
|
+
|
|
234
|
+
@app.post("/api/projects/refresh")
|
|
235
|
+
async def refresh_projects():
|
|
236
|
+
"""Force rescan of all project directories."""
|
|
237
|
+
projects = scan_projects()
|
|
238
|
+
return {"count": len(projects)}
|
|
239
|
+
|
|
240
|
+
|
|
148
241
|
@app.get("/api/memories")
|
|
149
242
|
async def list_memories(
|
|
150
243
|
project: str = Query(..., description="Path to the database file"),
|
|
@@ -160,23 +253,30 @@ async def list_memories(
|
|
|
160
253
|
offset: int = 0,
|
|
161
254
|
):
|
|
162
255
|
"""Get memories with filtering and pagination."""
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
256
|
+
try:
|
|
257
|
+
if not Path(project).exists():
|
|
258
|
+
log_error("/api/memories", FileNotFoundError("Database not found"), project=project)
|
|
259
|
+
raise HTTPException(status_code=404, detail="Database not found")
|
|
260
|
+
|
|
261
|
+
filters = FilterParams(
|
|
262
|
+
memory_type=memory_type,
|
|
263
|
+
status=status,
|
|
264
|
+
tags=tags.split(",") if tags else None,
|
|
265
|
+
search=search,
|
|
266
|
+
min_importance=min_importance,
|
|
267
|
+
max_importance=max_importance,
|
|
268
|
+
sort_by=sort_by,
|
|
269
|
+
sort_order=sort_order,
|
|
270
|
+
limit=limit,
|
|
271
|
+
offset=offset,
|
|
272
|
+
)
|
|
178
273
|
|
|
179
|
-
|
|
274
|
+
memories = get_memories(project, filters)
|
|
275
|
+
log_success("/api/memories", count=len(memories), offset=offset, filters=bool(search or memory_type))
|
|
276
|
+
return memories
|
|
277
|
+
except Exception as e:
|
|
278
|
+
log_error("/api/memories", e, project=project)
|
|
279
|
+
raise
|
|
180
280
|
|
|
181
281
|
|
|
182
282
|
# NOTE: These routes MUST be defined before /api/memories/{memory_id} to avoid path conflicts
|
|
@@ -237,16 +337,25 @@ async def update_memory_endpoint(
|
|
|
237
337
|
project: str = Query(..., description="Path to the database file"),
|
|
238
338
|
):
|
|
239
339
|
"""Update a memory."""
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
340
|
+
try:
|
|
341
|
+
if not Path(project).exists():
|
|
342
|
+
log_error("/api/memories/update", FileNotFoundError("Database not found"), memory_id=memory_id)
|
|
343
|
+
raise HTTPException(status_code=404, detail="Database not found")
|
|
344
|
+
|
|
345
|
+
updated = update_memory(project, memory_id, updates)
|
|
346
|
+
if not updated:
|
|
347
|
+
log_error("/api/memories/update", ValueError("Memory not found"), memory_id=memory_id)
|
|
348
|
+
raise HTTPException(status_code=404, detail="Memory not found")
|
|
349
|
+
|
|
350
|
+
# Notify connected clients
|
|
351
|
+
await manager.broadcast("memory_updated", updated.model_dump(by_alias=True))
|
|
352
|
+
log_success("/api/memories/update", memory_id=memory_id, fields_updated=len(updates.model_dump(exclude_unset=True)))
|
|
353
|
+
return updated
|
|
354
|
+
except HTTPException:
|
|
355
|
+
raise
|
|
356
|
+
except Exception as e:
|
|
357
|
+
log_error("/api/memories/update", e, memory_id=memory_id)
|
|
358
|
+
raise
|
|
250
359
|
|
|
251
360
|
|
|
252
361
|
@app.delete("/api/memories/{memory_id}")
|
|
@@ -255,16 +364,25 @@ async def delete_memory_endpoint(
|
|
|
255
364
|
project: str = Query(..., description="Path to the database file"),
|
|
256
365
|
):
|
|
257
366
|
"""Delete a memory."""
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
367
|
+
try:
|
|
368
|
+
if not Path(project).exists():
|
|
369
|
+
log_error("/api/memories/delete", FileNotFoundError("Database not found"), memory_id=memory_id)
|
|
370
|
+
raise HTTPException(status_code=404, detail="Database not found")
|
|
371
|
+
|
|
372
|
+
deleted = delete_memory(project, memory_id)
|
|
373
|
+
if not deleted:
|
|
374
|
+
log_error("/api/memories/delete", ValueError("Memory not found"), memory_id=memory_id)
|
|
375
|
+
raise HTTPException(status_code=404, detail="Memory not found")
|
|
376
|
+
|
|
377
|
+
# Notify connected clients
|
|
378
|
+
await manager.broadcast("memory_deleted", {"id": memory_id})
|
|
379
|
+
log_success("/api/memories/delete", memory_id=memory_id)
|
|
380
|
+
return {"message": "Memory deleted", "id": memory_id}
|
|
381
|
+
except HTTPException:
|
|
382
|
+
raise
|
|
383
|
+
except Exception as e:
|
|
384
|
+
log_error("/api/memories/delete", e, memory_id=memory_id)
|
|
385
|
+
raise
|
|
268
386
|
|
|
269
387
|
|
|
270
388
|
@app.get("/api/memories/stats/summary")
|
|
@@ -393,6 +511,63 @@ async def get_memory_growth_endpoint(
|
|
|
393
511
|
return get_memory_growth(project, days)
|
|
394
512
|
|
|
395
513
|
|
|
514
|
+
# --- Command Analytics Endpoints ---
|
|
515
|
+
|
|
516
|
+
|
|
517
|
+
@app.get("/api/stats/command-usage")
|
|
518
|
+
async def get_command_usage_endpoint(
|
|
519
|
+
project: str = Query(..., description="Path to the database file"),
|
|
520
|
+
scope: Optional[str] = Query(None, description="Filter by scope: 'universal' or 'project'"),
|
|
521
|
+
days: int = Query(30, ge=1, le=365),
|
|
522
|
+
):
|
|
523
|
+
"""Get slash command usage statistics."""
|
|
524
|
+
if not Path(project).exists():
|
|
525
|
+
raise HTTPException(status_code=404, detail="Database not found")
|
|
526
|
+
|
|
527
|
+
return get_command_usage(project, scope, days)
|
|
528
|
+
|
|
529
|
+
|
|
530
|
+
@app.get("/api/stats/skill-usage")
|
|
531
|
+
async def get_skill_usage_endpoint(
|
|
532
|
+
project: str = Query(..., description="Path to the database file"),
|
|
533
|
+
scope: Optional[str] = Query(None, description="Filter by scope: 'universal' or 'project'"),
|
|
534
|
+
days: int = Query(30, ge=1, le=365),
|
|
535
|
+
):
|
|
536
|
+
"""Get skill usage statistics."""
|
|
537
|
+
if not Path(project).exists():
|
|
538
|
+
raise HTTPException(status_code=404, detail="Database not found")
|
|
539
|
+
|
|
540
|
+
return get_skill_usage(project, scope, days)
|
|
541
|
+
|
|
542
|
+
|
|
543
|
+
@app.get("/api/stats/mcp-usage")
|
|
544
|
+
async def get_mcp_usage_endpoint(
|
|
545
|
+
project: str = Query(..., description="Path to the database file"),
|
|
546
|
+
days: int = Query(30, ge=1, le=365),
|
|
547
|
+
):
|
|
548
|
+
"""Get MCP server usage statistics."""
|
|
549
|
+
if not Path(project).exists():
|
|
550
|
+
raise HTTPException(status_code=404, detail="Database not found")
|
|
551
|
+
|
|
552
|
+
return get_mcp_usage(project, days)
|
|
553
|
+
|
|
554
|
+
|
|
555
|
+
@app.get("/api/activities/{activity_id}")
|
|
556
|
+
async def get_activity_detail_endpoint(
|
|
557
|
+
activity_id: str,
|
|
558
|
+
project: str = Query(..., description="Path to the database file"),
|
|
559
|
+
):
|
|
560
|
+
"""Get full activity details including complete input/output."""
|
|
561
|
+
if not Path(project).exists():
|
|
562
|
+
raise HTTPException(status_code=404, detail="Database not found")
|
|
563
|
+
|
|
564
|
+
activity = get_activity_detail(project, activity_id)
|
|
565
|
+
if not activity:
|
|
566
|
+
raise HTTPException(status_code=404, detail="Activity not found")
|
|
567
|
+
|
|
568
|
+
return activity
|
|
569
|
+
|
|
570
|
+
|
|
396
571
|
# --- Session Context Endpoints ---
|
|
397
572
|
|
|
398
573
|
|
|
@@ -454,16 +629,184 @@ async def chat_with_memories(
|
|
|
454
629
|
project: str = Query(..., description="Path to the database file"),
|
|
455
630
|
):
|
|
456
631
|
"""Ask a natural language question about memories."""
|
|
632
|
+
try:
|
|
633
|
+
if not Path(project).exists():
|
|
634
|
+
log_error("/api/chat", FileNotFoundError("Database not found"), question=request.question[:50])
|
|
635
|
+
raise HTTPException(status_code=404, detail="Database not found")
|
|
636
|
+
|
|
637
|
+
result = await chat_service.ask_about_memories(
|
|
638
|
+
project,
|
|
639
|
+
request.question,
|
|
640
|
+
request.max_memories,
|
|
641
|
+
)
|
|
642
|
+
|
|
643
|
+
log_success("/api/chat", question_len=len(request.question), sources=len(result.get("sources", [])))
|
|
644
|
+
return ChatResponse(**result)
|
|
645
|
+
except HTTPException:
|
|
646
|
+
raise
|
|
647
|
+
except Exception as e:
|
|
648
|
+
log_error("/api/chat", e, question=request.question[:50])
|
|
649
|
+
raise
|
|
650
|
+
|
|
651
|
+
|
|
652
|
+
@app.get("/api/chat/stream")
|
|
653
|
+
async def stream_chat(
|
|
654
|
+
project: str = Query(..., description="Path to the database file"),
|
|
655
|
+
question: str = Query(..., description="The question to ask"),
|
|
656
|
+
max_memories: int = Query(10, ge=1, le=50),
|
|
657
|
+
):
|
|
658
|
+
"""SSE endpoint for streaming chat responses."""
|
|
659
|
+
from fastapi.responses import StreamingResponse
|
|
660
|
+
|
|
457
661
|
if not Path(project).exists():
|
|
458
662
|
raise HTTPException(status_code=404, detail="Database not found")
|
|
459
663
|
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
664
|
+
async def event_generator():
|
|
665
|
+
try:
|
|
666
|
+
async for event in chat_service.stream_ask_about_memories(project, question, max_memories):
|
|
667
|
+
yield f"data: {json.dumps(event)}\n\n"
|
|
668
|
+
except Exception as e:
|
|
669
|
+
yield f"data: {json.dumps({'type': 'error', 'data': str(e)})}\n\n"
|
|
670
|
+
|
|
671
|
+
return StreamingResponse(
|
|
672
|
+
event_generator(),
|
|
673
|
+
media_type="text/event-stream",
|
|
674
|
+
headers={
|
|
675
|
+
"Cache-Control": "no-cache",
|
|
676
|
+
"Connection": "keep-alive",
|
|
677
|
+
"X-Accel-Buffering": "no",
|
|
678
|
+
}
|
|
464
679
|
)
|
|
465
680
|
|
|
466
|
-
|
|
681
|
+
|
|
682
|
+
@app.post("/api/chat/save", response_model=ConversationSaveResponse)
|
|
683
|
+
async def save_chat_conversation(
|
|
684
|
+
request: ConversationSaveRequest,
|
|
685
|
+
project: str = Query(..., description="Path to the database file"),
|
|
686
|
+
):
|
|
687
|
+
"""Save a chat conversation as a memory."""
|
|
688
|
+
try:
|
|
689
|
+
if not Path(project).exists():
|
|
690
|
+
log_error("/api/chat/save", FileNotFoundError("Database not found"))
|
|
691
|
+
raise HTTPException(status_code=404, detail="Database not found")
|
|
692
|
+
|
|
693
|
+
result = await chat_service.save_conversation(
|
|
694
|
+
project,
|
|
695
|
+
[msg.model_dump() for msg in request.messages],
|
|
696
|
+
request.referenced_memory_ids,
|
|
697
|
+
request.importance or 60,
|
|
698
|
+
)
|
|
699
|
+
|
|
700
|
+
log_success("/api/chat/save", memory_id=result["memory_id"], messages=len(request.messages))
|
|
701
|
+
return ConversationSaveResponse(**result)
|
|
702
|
+
except HTTPException:
|
|
703
|
+
raise
|
|
704
|
+
except Exception as e:
|
|
705
|
+
log_error("/api/chat/save", e)
|
|
706
|
+
raise
|
|
707
|
+
|
|
708
|
+
|
|
709
|
+
# --- Image Generation Endpoints ---
|
|
710
|
+
|
|
711
|
+
|
|
712
|
+
@app.get("/api/image/status")
|
|
713
|
+
async def get_image_status():
|
|
714
|
+
"""Check if image generation is available."""
|
|
715
|
+
return {
|
|
716
|
+
"available": image_service.is_available(),
|
|
717
|
+
"message": "Image generation ready" if image_service.is_available()
|
|
718
|
+
else "Configure GEMINI_API_KEY and install google-genai for image generation",
|
|
719
|
+
}
|
|
720
|
+
|
|
721
|
+
|
|
722
|
+
@app.get("/api/image/presets")
|
|
723
|
+
async def get_image_presets():
|
|
724
|
+
"""Get available image preset templates."""
|
|
725
|
+
return {"presets": image_service.get_presets()}
|
|
726
|
+
|
|
727
|
+
|
|
728
|
+
@app.post("/api/image/generate-batch", response_model=BatchImageGenerationResponse)
|
|
729
|
+
async def generate_images_batch(
|
|
730
|
+
request: BatchImageGenerationRequest,
|
|
731
|
+
db_path: str = Query(..., alias="project", description="Path to the database file"),
|
|
732
|
+
):
|
|
733
|
+
"""Generate multiple images with different presets/prompts."""
|
|
734
|
+
# Validate image count
|
|
735
|
+
if len(request.images) not in [1, 2, 4]:
|
|
736
|
+
return BatchImageGenerationResponse(
|
|
737
|
+
success=False,
|
|
738
|
+
errors=["Must request 1, 2, or 4 images"]
|
|
739
|
+
)
|
|
740
|
+
|
|
741
|
+
# Build memory context
|
|
742
|
+
memory_context = ""
|
|
743
|
+
if request.memory_ids:
|
|
744
|
+
memory_context = image_service.build_memory_context(db_path, request.memory_ids)
|
|
745
|
+
|
|
746
|
+
# Build chat context
|
|
747
|
+
chat_context = image_service.build_chat_context(request.chat_messages)
|
|
748
|
+
|
|
749
|
+
# Convert request models to internal format
|
|
750
|
+
image_requests = [
|
|
751
|
+
SingleImageRequest(
|
|
752
|
+
preset=ImagePreset(img.preset),
|
|
753
|
+
custom_prompt=img.custom_prompt,
|
|
754
|
+
aspect_ratio=img.aspect_ratio,
|
|
755
|
+
image_size=img.image_size
|
|
756
|
+
)
|
|
757
|
+
for img in request.images
|
|
758
|
+
]
|
|
759
|
+
|
|
760
|
+
result = await image_service.generate_batch(
|
|
761
|
+
requests=image_requests,
|
|
762
|
+
memory_context=memory_context,
|
|
763
|
+
chat_context=chat_context,
|
|
764
|
+
use_search_grounding=request.use_search_grounding
|
|
765
|
+
)
|
|
766
|
+
|
|
767
|
+
return BatchImageGenerationResponse(
|
|
768
|
+
success=result.success,
|
|
769
|
+
images=[
|
|
770
|
+
SingleImageResponseModel(
|
|
771
|
+
success=img.success,
|
|
772
|
+
image_data=img.image_data,
|
|
773
|
+
text_response=img.text_response,
|
|
774
|
+
thought_signature=img.thought_signature,
|
|
775
|
+
image_id=img.image_id,
|
|
776
|
+
error=img.error,
|
|
777
|
+
index=img.index
|
|
778
|
+
)
|
|
779
|
+
for img in result.images
|
|
780
|
+
],
|
|
781
|
+
errors=result.errors
|
|
782
|
+
)
|
|
783
|
+
|
|
784
|
+
|
|
785
|
+
@app.post("/api/image/refine", response_model=SingleImageResponseModel)
|
|
786
|
+
async def refine_image(request: ImageRefineRequest):
|
|
787
|
+
"""Refine an existing generated image with a new prompt."""
|
|
788
|
+
result = await image_service.refine_image(
|
|
789
|
+
image_id=request.image_id,
|
|
790
|
+
refinement_prompt=request.refinement_prompt,
|
|
791
|
+
aspect_ratio=request.aspect_ratio,
|
|
792
|
+
image_size=request.image_size
|
|
793
|
+
)
|
|
794
|
+
|
|
795
|
+
return SingleImageResponseModel(
|
|
796
|
+
success=result.success,
|
|
797
|
+
image_data=result.image_data,
|
|
798
|
+
text_response=result.text_response,
|
|
799
|
+
thought_signature=result.thought_signature,
|
|
800
|
+
image_id=result.image_id,
|
|
801
|
+
error=result.error
|
|
802
|
+
)
|
|
803
|
+
|
|
804
|
+
|
|
805
|
+
@app.post("/api/image/clear-conversation")
|
|
806
|
+
async def clear_image_conversation(image_id: Optional[str] = None):
|
|
807
|
+
"""Clear image conversation history. If image_id provided, clear only that image."""
|
|
808
|
+
image_service.clear_conversation(image_id)
|
|
809
|
+
return {"status": "cleared", "image_id": image_id}
|
|
467
810
|
|
|
468
811
|
|
|
469
812
|
# --- WebSocket Endpoint ---
|
{omni_cortex-1.0.4.data → omni_cortex-1.3.0.data}/data/share/omni-cortex/dashboard/backend/models.py
RENAMED
|
@@ -15,6 +15,31 @@ class ProjectInfo(BaseModel):
|
|
|
15
15
|
last_modified: Optional[datetime] = None
|
|
16
16
|
memory_count: int = 0
|
|
17
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
|
|
18
43
|
|
|
19
44
|
|
|
20
45
|
class Memory(BaseModel):
|
|
@@ -138,3 +163,71 @@ class ChatResponse(BaseModel):
|
|
|
138
163
|
answer: str
|
|
139
164
|
sources: list[ChatSource]
|
|
140
165
|
error: Optional[str] = None
|
|
166
|
+
|
|
167
|
+
|
|
168
|
+
class ConversationMessage(BaseModel):
|
|
169
|
+
"""A message in a conversation."""
|
|
170
|
+
|
|
171
|
+
role: str # 'user' or 'assistant'
|
|
172
|
+
content: str
|
|
173
|
+
timestamp: str
|
|
174
|
+
|
|
175
|
+
|
|
176
|
+
class ConversationSaveRequest(BaseModel):
|
|
177
|
+
"""Request to save a conversation as memory."""
|
|
178
|
+
|
|
179
|
+
messages: list[ConversationMessage]
|
|
180
|
+
referenced_memory_ids: Optional[list[str]] = None
|
|
181
|
+
importance: Optional[int] = Field(default=60, ge=1, le=100)
|
|
182
|
+
|
|
183
|
+
|
|
184
|
+
class ConversationSaveResponse(BaseModel):
|
|
185
|
+
"""Response after saving a conversation."""
|
|
186
|
+
|
|
187
|
+
memory_id: str
|
|
188
|
+
summary: str
|
|
189
|
+
|
|
190
|
+
|
|
191
|
+
# --- Image Generation Models ---
|
|
192
|
+
|
|
193
|
+
|
|
194
|
+
class SingleImageRequestModel(BaseModel):
|
|
195
|
+
"""Request for a single image in a batch."""
|
|
196
|
+
preset: str = "custom" # Maps to ImagePreset enum
|
|
197
|
+
custom_prompt: str = ""
|
|
198
|
+
aspect_ratio: str = "16:9"
|
|
199
|
+
image_size: str = "2K"
|
|
200
|
+
|
|
201
|
+
|
|
202
|
+
class BatchImageGenerationRequest(BaseModel):
|
|
203
|
+
"""Request for generating multiple images."""
|
|
204
|
+
images: list[SingleImageRequestModel] # 1, 2, or 4 images
|
|
205
|
+
memory_ids: list[str] = []
|
|
206
|
+
chat_messages: list[dict] = [] # Recent chat for context
|
|
207
|
+
use_search_grounding: bool = False
|
|
208
|
+
|
|
209
|
+
|
|
210
|
+
class ImageRefineRequest(BaseModel):
|
|
211
|
+
"""Request for refining an existing image."""
|
|
212
|
+
image_id: str
|
|
213
|
+
refinement_prompt: str
|
|
214
|
+
aspect_ratio: Optional[str] = None
|
|
215
|
+
image_size: Optional[str] = None
|
|
216
|
+
|
|
217
|
+
|
|
218
|
+
class SingleImageResponseModel(BaseModel):
|
|
219
|
+
"""Response for a single generated image."""
|
|
220
|
+
success: bool
|
|
221
|
+
image_data: Optional[str] = None # Base64 encoded
|
|
222
|
+
text_response: Optional[str] = None
|
|
223
|
+
thought_signature: Optional[str] = None
|
|
224
|
+
image_id: Optional[str] = None
|
|
225
|
+
error: Optional[str] = None
|
|
226
|
+
index: int = 0
|
|
227
|
+
|
|
228
|
+
|
|
229
|
+
class BatchImageGenerationResponse(BaseModel):
|
|
230
|
+
"""Response for batch image generation."""
|
|
231
|
+
success: bool
|
|
232
|
+
images: list[SingleImageResponseModel] = []
|
|
233
|
+
errors: list[str] = []
|