truthound-dashboard 1.0.2__py3-none-any.whl → 1.2.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 (33) hide show
  1. truthound_dashboard/api/catalog.py +343 -0
  2. truthound_dashboard/api/collaboration.py +148 -0
  3. truthound_dashboard/api/glossary.py +329 -0
  4. truthound_dashboard/api/router.py +29 -0
  5. truthound_dashboard/cli.py +397 -0
  6. truthound_dashboard/core/__init__.py +12 -0
  7. truthound_dashboard/core/phase5/__init__.py +17 -0
  8. truthound_dashboard/core/phase5/activity.py +144 -0
  9. truthound_dashboard/core/phase5/catalog.py +868 -0
  10. truthound_dashboard/core/phase5/collaboration.py +305 -0
  11. truthound_dashboard/core/phase5/glossary.py +828 -0
  12. truthound_dashboard/db/__init__.py +37 -0
  13. truthound_dashboard/db/models.py +693 -0
  14. truthound_dashboard/schemas/__init__.py +114 -0
  15. truthound_dashboard/schemas/catalog.py +352 -0
  16. truthound_dashboard/schemas/collaboration.py +169 -0
  17. truthound_dashboard/schemas/glossary.py +349 -0
  18. truthound_dashboard/translate/__init__.py +61 -0
  19. truthound_dashboard/translate/config_updater.py +327 -0
  20. truthound_dashboard/translate/exceptions.py +98 -0
  21. truthound_dashboard/translate/providers/__init__.py +49 -0
  22. truthound_dashboard/translate/providers/anthropic.py +135 -0
  23. truthound_dashboard/translate/providers/base.py +225 -0
  24. truthound_dashboard/translate/providers/mistral.py +138 -0
  25. truthound_dashboard/translate/providers/ollama.py +226 -0
  26. truthound_dashboard/translate/providers/openai.py +187 -0
  27. truthound_dashboard/translate/providers/registry.py +217 -0
  28. truthound_dashboard/translate/translator.py +443 -0
  29. {truthound_dashboard-1.0.2.dist-info → truthound_dashboard-1.2.0.dist-info}/METADATA +123 -4
  30. {truthound_dashboard-1.0.2.dist-info → truthound_dashboard-1.2.0.dist-info}/RECORD +33 -11
  31. {truthound_dashboard-1.0.2.dist-info → truthound_dashboard-1.2.0.dist-info}/WHEEL +0 -0
  32. {truthound_dashboard-1.0.2.dist-info → truthound_dashboard-1.2.0.dist-info}/entry_points.txt +0 -0
  33. {truthound_dashboard-1.0.2.dist-info → truthound_dashboard-1.2.0.dist-info}/licenses/LICENSE +0 -0
@@ -0,0 +1,305 @@
1
+ """Collaboration service for Phase 5.
2
+
3
+ Provides business logic for managing comments and activity feeds.
4
+ """
5
+
6
+ from __future__ import annotations
7
+
8
+ from collections.abc import Sequence
9
+
10
+ from sqlalchemy import select
11
+ from sqlalchemy.ext.asyncio import AsyncSession
12
+
13
+ from truthound_dashboard.db import (
14
+ Activity,
15
+ ActivityAction,
16
+ BaseRepository,
17
+ Comment,
18
+ ResourceType,
19
+ )
20
+
21
+ from .activity import ActivityLogger, ActivityRepository
22
+
23
+
24
+ # =============================================================================
25
+ # Repositories
26
+ # =============================================================================
27
+
28
+
29
+ class CommentRepository(BaseRepository[Comment]):
30
+ """Repository for Comment model operations."""
31
+
32
+ model = Comment
33
+
34
+ async def get_for_resource(
35
+ self,
36
+ resource_type: str,
37
+ resource_id: str,
38
+ *,
39
+ limit: int = 100,
40
+ ) -> list[Comment]:
41
+ """Get comments for a resource.
42
+
43
+ Args:
44
+ resource_type: Type of resource.
45
+ resource_id: Resource ID.
46
+ limit: Maximum to return.
47
+
48
+ Returns:
49
+ List of root comments (with replies loaded).
50
+ """
51
+ result = await self.session.execute(
52
+ select(Comment)
53
+ .where(Comment.resource_type == resource_type)
54
+ .where(Comment.resource_id == resource_id)
55
+ .where(Comment.parent_id.is_(None)) # Only root comments
56
+ .order_by(Comment.created_at.desc())
57
+ .limit(limit)
58
+ )
59
+ return list(result.scalars().all())
60
+
61
+ async def count_for_resource(
62
+ self,
63
+ resource_type: str,
64
+ resource_id: str,
65
+ ) -> int:
66
+ """Count comments for a resource.
67
+
68
+ Args:
69
+ resource_type: Type of resource.
70
+ resource_id: Resource ID.
71
+
72
+ Returns:
73
+ Total count including replies.
74
+ """
75
+ return await self.count([
76
+ Comment.resource_type == resource_type,
77
+ Comment.resource_id == resource_id,
78
+ ])
79
+
80
+
81
+ # =============================================================================
82
+ # Service
83
+ # =============================================================================
84
+
85
+
86
+ class CollaborationService:
87
+ """Service for managing collaboration features.
88
+
89
+ Handles comments and activity feeds.
90
+ """
91
+
92
+ def __init__(self, session: AsyncSession) -> None:
93
+ """Initialize service.
94
+
95
+ Args:
96
+ session: Database session.
97
+ """
98
+ self.session = session
99
+ self.comment_repo = CommentRepository(session)
100
+ self.activity_repo = ActivityRepository(session)
101
+ self.activity_logger = ActivityLogger(session)
102
+
103
+ # =========================================================================
104
+ # Comment Operations
105
+ # =========================================================================
106
+
107
+ async def get_comments(
108
+ self,
109
+ resource_type: str,
110
+ resource_id: str,
111
+ *,
112
+ limit: int = 100,
113
+ ) -> tuple[list[Comment], int]:
114
+ """Get comments for a resource.
115
+
116
+ Args:
117
+ resource_type: Type of resource.
118
+ resource_id: Resource ID.
119
+ limit: Maximum to return.
120
+
121
+ Returns:
122
+ Tuple of (comments, total_count).
123
+ """
124
+ comments = await self.comment_repo.get_for_resource(
125
+ resource_type,
126
+ resource_id,
127
+ limit=limit,
128
+ )
129
+ total = await self.comment_repo.count_for_resource(
130
+ resource_type,
131
+ resource_id,
132
+ )
133
+ return comments, total
134
+
135
+ async def get_comment(self, comment_id: str) -> Comment | None:
136
+ """Get comment by ID.
137
+
138
+ Args:
139
+ comment_id: Comment ID.
140
+
141
+ Returns:
142
+ Comment or None.
143
+ """
144
+ return await self.comment_repo.get_by_id(comment_id)
145
+
146
+ async def create_comment(
147
+ self,
148
+ *,
149
+ resource_type: str,
150
+ resource_id: str,
151
+ content: str,
152
+ author_id: str | None = None,
153
+ parent_id: str | None = None,
154
+ ) -> Comment:
155
+ """Create a new comment.
156
+
157
+ Args:
158
+ resource_type: Type of resource.
159
+ resource_id: Resource ID.
160
+ content: Comment content.
161
+ author_id: Author identifier.
162
+ parent_id: Parent comment ID for replies.
163
+
164
+ Returns:
165
+ Created comment.
166
+
167
+ Raises:
168
+ ValueError: If parent comment not found or mismatched resource.
169
+ """
170
+ # Validate parent if provided
171
+ if parent_id:
172
+ parent = await self.comment_repo.get_by_id(parent_id)
173
+ if not parent:
174
+ raise ValueError(f"Parent comment '{parent_id}' not found")
175
+ # Ensure reply is on same resource
176
+ if parent.resource_type != resource_type or parent.resource_id != resource_id:
177
+ raise ValueError("Reply must be on the same resource as parent")
178
+
179
+ comment = await self.comment_repo.create(
180
+ resource_type=resource_type,
181
+ resource_id=resource_id,
182
+ content=content,
183
+ author_id=author_id,
184
+ parent_id=parent_id,
185
+ )
186
+
187
+ await self.activity_logger.log(
188
+ resource_type,
189
+ resource_id,
190
+ ActivityAction.COMMENTED,
191
+ actor_id=author_id,
192
+ description="Added a comment",
193
+ metadata={"comment_id": comment.id},
194
+ )
195
+
196
+ return comment
197
+
198
+ async def update_comment(
199
+ self,
200
+ comment_id: str,
201
+ *,
202
+ content: str,
203
+ actor_id: str | None = None,
204
+ ) -> Comment | None:
205
+ """Update a comment.
206
+
207
+ Args:
208
+ comment_id: Comment ID.
209
+ content: New content.
210
+ actor_id: User updating the comment.
211
+
212
+ Returns:
213
+ Updated comment or None.
214
+ """
215
+ comment = await self.comment_repo.get_by_id(comment_id)
216
+ if not comment:
217
+ return None
218
+
219
+ comment.content = content
220
+ await self.session.flush()
221
+ await self.session.refresh(comment)
222
+
223
+ return comment
224
+
225
+ async def delete_comment(
226
+ self,
227
+ comment_id: str,
228
+ *,
229
+ actor_id: str | None = None,
230
+ ) -> bool:
231
+ """Delete a comment.
232
+
233
+ Args:
234
+ comment_id: Comment ID.
235
+ actor_id: User deleting the comment.
236
+
237
+ Returns:
238
+ True if deleted.
239
+ """
240
+ comment = await self.comment_repo.get_by_id(comment_id)
241
+ if not comment:
242
+ return False
243
+
244
+ resource_type = comment.resource_type
245
+ resource_id = comment.resource_id
246
+
247
+ deleted = await self.comment_repo.delete(comment_id)
248
+
249
+ if deleted:
250
+ await self.activity_logger.log(
251
+ resource_type,
252
+ resource_id,
253
+ ActivityAction.DELETED,
254
+ actor_id=actor_id,
255
+ description="Deleted a comment",
256
+ )
257
+
258
+ return deleted
259
+
260
+ # =========================================================================
261
+ # Activity Operations
262
+ # =========================================================================
263
+
264
+ async def get_activities(
265
+ self,
266
+ *,
267
+ resource_type: str | None = None,
268
+ resource_id: str | None = None,
269
+ limit: int = 50,
270
+ ) -> list[Activity]:
271
+ """Get activities with optional filters.
272
+
273
+ Args:
274
+ resource_type: Filter by resource type.
275
+ resource_id: Filter by resource ID.
276
+ limit: Maximum to return.
277
+
278
+ Returns:
279
+ List of activities.
280
+ """
281
+ if resource_type and resource_id:
282
+ return await self.activity_logger.get_for_resource(
283
+ resource_type,
284
+ resource_id,
285
+ limit=limit,
286
+ )
287
+ return await self.activity_logger.get_recent(
288
+ resource_type=resource_type,
289
+ limit=limit,
290
+ )
291
+
292
+ async def get_recent_activities(
293
+ self,
294
+ *,
295
+ limit: int = 50,
296
+ ) -> list[Activity]:
297
+ """Get recent activities across all resources.
298
+
299
+ Args:
300
+ limit: Maximum to return.
301
+
302
+ Returns:
303
+ List of recent activities.
304
+ """
305
+ return await self.activity_logger.get_recent(limit=limit)