remdb 0.3.242__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.

Potentially problematic release.


This version of remdb might be problematic. Click here for more details.

Files changed (235) hide show
  1. rem/__init__.py +129 -0
  2. rem/agentic/README.md +760 -0
  3. rem/agentic/__init__.py +54 -0
  4. rem/agentic/agents/README.md +155 -0
  5. rem/agentic/agents/__init__.py +38 -0
  6. rem/agentic/agents/agent_manager.py +311 -0
  7. rem/agentic/agents/sse_simulator.py +502 -0
  8. rem/agentic/context.py +425 -0
  9. rem/agentic/context_builder.py +360 -0
  10. rem/agentic/llm_provider_models.py +301 -0
  11. rem/agentic/mcp/__init__.py +0 -0
  12. rem/agentic/mcp/tool_wrapper.py +273 -0
  13. rem/agentic/otel/__init__.py +5 -0
  14. rem/agentic/otel/setup.py +240 -0
  15. rem/agentic/providers/phoenix.py +926 -0
  16. rem/agentic/providers/pydantic_ai.py +854 -0
  17. rem/agentic/query.py +117 -0
  18. rem/agentic/query_helper.py +89 -0
  19. rem/agentic/schema.py +737 -0
  20. rem/agentic/serialization.py +245 -0
  21. rem/agentic/tools/__init__.py +5 -0
  22. rem/agentic/tools/rem_tools.py +242 -0
  23. rem/api/README.md +657 -0
  24. rem/api/deps.py +253 -0
  25. rem/api/main.py +460 -0
  26. rem/api/mcp_router/prompts.py +182 -0
  27. rem/api/mcp_router/resources.py +820 -0
  28. rem/api/mcp_router/server.py +243 -0
  29. rem/api/mcp_router/tools.py +1605 -0
  30. rem/api/middleware/tracking.py +172 -0
  31. rem/api/routers/admin.py +520 -0
  32. rem/api/routers/auth.py +898 -0
  33. rem/api/routers/chat/__init__.py +5 -0
  34. rem/api/routers/chat/child_streaming.py +394 -0
  35. rem/api/routers/chat/completions.py +702 -0
  36. rem/api/routers/chat/json_utils.py +76 -0
  37. rem/api/routers/chat/models.py +202 -0
  38. rem/api/routers/chat/otel_utils.py +33 -0
  39. rem/api/routers/chat/sse_events.py +546 -0
  40. rem/api/routers/chat/streaming.py +950 -0
  41. rem/api/routers/chat/streaming_utils.py +327 -0
  42. rem/api/routers/common.py +18 -0
  43. rem/api/routers/dev.py +87 -0
  44. rem/api/routers/feedback.py +276 -0
  45. rem/api/routers/messages.py +620 -0
  46. rem/api/routers/models.py +86 -0
  47. rem/api/routers/query.py +362 -0
  48. rem/api/routers/shared_sessions.py +422 -0
  49. rem/auth/README.md +258 -0
  50. rem/auth/__init__.py +36 -0
  51. rem/auth/jwt.py +367 -0
  52. rem/auth/middleware.py +318 -0
  53. rem/auth/providers/__init__.py +16 -0
  54. rem/auth/providers/base.py +376 -0
  55. rem/auth/providers/email.py +215 -0
  56. rem/auth/providers/google.py +163 -0
  57. rem/auth/providers/microsoft.py +237 -0
  58. rem/cli/README.md +517 -0
  59. rem/cli/__init__.py +8 -0
  60. rem/cli/commands/README.md +299 -0
  61. rem/cli/commands/__init__.py +3 -0
  62. rem/cli/commands/ask.py +549 -0
  63. rem/cli/commands/cluster.py +1808 -0
  64. rem/cli/commands/configure.py +495 -0
  65. rem/cli/commands/db.py +828 -0
  66. rem/cli/commands/dreaming.py +324 -0
  67. rem/cli/commands/experiments.py +1698 -0
  68. rem/cli/commands/mcp.py +66 -0
  69. rem/cli/commands/process.py +388 -0
  70. rem/cli/commands/query.py +109 -0
  71. rem/cli/commands/scaffold.py +47 -0
  72. rem/cli/commands/schema.py +230 -0
  73. rem/cli/commands/serve.py +106 -0
  74. rem/cli/commands/session.py +453 -0
  75. rem/cli/dreaming.py +363 -0
  76. rem/cli/main.py +123 -0
  77. rem/config.py +244 -0
  78. rem/mcp_server.py +41 -0
  79. rem/models/core/__init__.py +49 -0
  80. rem/models/core/core_model.py +70 -0
  81. rem/models/core/engram.py +333 -0
  82. rem/models/core/experiment.py +672 -0
  83. rem/models/core/inline_edge.py +132 -0
  84. rem/models/core/rem_query.py +246 -0
  85. rem/models/entities/__init__.py +68 -0
  86. rem/models/entities/domain_resource.py +38 -0
  87. rem/models/entities/feedback.py +123 -0
  88. rem/models/entities/file.py +57 -0
  89. rem/models/entities/image_resource.py +88 -0
  90. rem/models/entities/message.py +64 -0
  91. rem/models/entities/moment.py +123 -0
  92. rem/models/entities/ontology.py +181 -0
  93. rem/models/entities/ontology_config.py +131 -0
  94. rem/models/entities/resource.py +95 -0
  95. rem/models/entities/schema.py +87 -0
  96. rem/models/entities/session.py +84 -0
  97. rem/models/entities/shared_session.py +180 -0
  98. rem/models/entities/subscriber.py +175 -0
  99. rem/models/entities/user.py +93 -0
  100. rem/py.typed +0 -0
  101. rem/registry.py +373 -0
  102. rem/schemas/README.md +507 -0
  103. rem/schemas/__init__.py +6 -0
  104. rem/schemas/agents/README.md +92 -0
  105. rem/schemas/agents/core/agent-builder.yaml +235 -0
  106. rem/schemas/agents/core/moment-builder.yaml +178 -0
  107. rem/schemas/agents/core/rem-query-agent.yaml +226 -0
  108. rem/schemas/agents/core/resource-affinity-assessor.yaml +99 -0
  109. rem/schemas/agents/core/simple-assistant.yaml +19 -0
  110. rem/schemas/agents/core/user-profile-builder.yaml +163 -0
  111. rem/schemas/agents/examples/contract-analyzer.yaml +317 -0
  112. rem/schemas/agents/examples/contract-extractor.yaml +134 -0
  113. rem/schemas/agents/examples/cv-parser.yaml +263 -0
  114. rem/schemas/agents/examples/hello-world.yaml +37 -0
  115. rem/schemas/agents/examples/query.yaml +54 -0
  116. rem/schemas/agents/examples/simple.yaml +21 -0
  117. rem/schemas/agents/examples/test.yaml +29 -0
  118. rem/schemas/agents/rem.yaml +132 -0
  119. rem/schemas/evaluators/hello-world/default.yaml +77 -0
  120. rem/schemas/evaluators/rem/faithfulness.yaml +219 -0
  121. rem/schemas/evaluators/rem/lookup-correctness.yaml +182 -0
  122. rem/schemas/evaluators/rem/retrieval-precision.yaml +199 -0
  123. rem/schemas/evaluators/rem/retrieval-recall.yaml +211 -0
  124. rem/schemas/evaluators/rem/search-correctness.yaml +192 -0
  125. rem/services/__init__.py +18 -0
  126. rem/services/audio/INTEGRATION.md +308 -0
  127. rem/services/audio/README.md +376 -0
  128. rem/services/audio/__init__.py +15 -0
  129. rem/services/audio/chunker.py +354 -0
  130. rem/services/audio/transcriber.py +259 -0
  131. rem/services/content/README.md +1269 -0
  132. rem/services/content/__init__.py +5 -0
  133. rem/services/content/providers.py +760 -0
  134. rem/services/content/service.py +762 -0
  135. rem/services/dreaming/README.md +230 -0
  136. rem/services/dreaming/__init__.py +53 -0
  137. rem/services/dreaming/affinity_service.py +322 -0
  138. rem/services/dreaming/moment_service.py +251 -0
  139. rem/services/dreaming/ontology_service.py +54 -0
  140. rem/services/dreaming/user_model_service.py +297 -0
  141. rem/services/dreaming/utils.py +39 -0
  142. rem/services/email/__init__.py +10 -0
  143. rem/services/email/service.py +522 -0
  144. rem/services/email/templates.py +360 -0
  145. rem/services/embeddings/__init__.py +11 -0
  146. rem/services/embeddings/api.py +127 -0
  147. rem/services/embeddings/worker.py +435 -0
  148. rem/services/fs/README.md +662 -0
  149. rem/services/fs/__init__.py +62 -0
  150. rem/services/fs/examples.py +206 -0
  151. rem/services/fs/examples_paths.py +204 -0
  152. rem/services/fs/git_provider.py +935 -0
  153. rem/services/fs/local_provider.py +760 -0
  154. rem/services/fs/parsing-hooks-examples.md +172 -0
  155. rem/services/fs/paths.py +276 -0
  156. rem/services/fs/provider.py +460 -0
  157. rem/services/fs/s3_provider.py +1042 -0
  158. rem/services/fs/service.py +186 -0
  159. rem/services/git/README.md +1075 -0
  160. rem/services/git/__init__.py +17 -0
  161. rem/services/git/service.py +469 -0
  162. rem/services/phoenix/EXPERIMENT_DESIGN.md +1146 -0
  163. rem/services/phoenix/README.md +453 -0
  164. rem/services/phoenix/__init__.py +46 -0
  165. rem/services/phoenix/client.py +960 -0
  166. rem/services/phoenix/config.py +88 -0
  167. rem/services/phoenix/prompt_labels.py +477 -0
  168. rem/services/postgres/README.md +757 -0
  169. rem/services/postgres/__init__.py +49 -0
  170. rem/services/postgres/diff_service.py +599 -0
  171. rem/services/postgres/migration_service.py +427 -0
  172. rem/services/postgres/programmable_diff_service.py +635 -0
  173. rem/services/postgres/pydantic_to_sqlalchemy.py +562 -0
  174. rem/services/postgres/register_type.py +353 -0
  175. rem/services/postgres/repository.py +481 -0
  176. rem/services/postgres/schema_generator.py +661 -0
  177. rem/services/postgres/service.py +802 -0
  178. rem/services/postgres/sql_builder.py +355 -0
  179. rem/services/rate_limit.py +113 -0
  180. rem/services/rem/README.md +318 -0
  181. rem/services/rem/__init__.py +23 -0
  182. rem/services/rem/exceptions.py +71 -0
  183. rem/services/rem/executor.py +293 -0
  184. rem/services/rem/parser.py +180 -0
  185. rem/services/rem/queries.py +196 -0
  186. rem/services/rem/query.py +371 -0
  187. rem/services/rem/service.py +608 -0
  188. rem/services/session/README.md +374 -0
  189. rem/services/session/__init__.py +13 -0
  190. rem/services/session/compression.py +488 -0
  191. rem/services/session/pydantic_messages.py +310 -0
  192. rem/services/session/reload.py +85 -0
  193. rem/services/user_service.py +130 -0
  194. rem/settings.py +1877 -0
  195. rem/sql/background_indexes.sql +52 -0
  196. rem/sql/migrations/001_install.sql +983 -0
  197. rem/sql/migrations/002_install_models.sql +3157 -0
  198. rem/sql/migrations/003_optional_extensions.sql +326 -0
  199. rem/sql/migrations/004_cache_system.sql +282 -0
  200. rem/sql/migrations/005_schema_update.sql +145 -0
  201. rem/sql/migrations/migrate_session_id_to_uuid.sql +45 -0
  202. rem/utils/AGENTIC_CHUNKING.md +597 -0
  203. rem/utils/README.md +628 -0
  204. rem/utils/__init__.py +61 -0
  205. rem/utils/agentic_chunking.py +622 -0
  206. rem/utils/batch_ops.py +343 -0
  207. rem/utils/chunking.py +108 -0
  208. rem/utils/clip_embeddings.py +276 -0
  209. rem/utils/constants.py +97 -0
  210. rem/utils/date_utils.py +228 -0
  211. rem/utils/dict_utils.py +98 -0
  212. rem/utils/embeddings.py +436 -0
  213. rem/utils/examples/embeddings_example.py +305 -0
  214. rem/utils/examples/sql_types_example.py +202 -0
  215. rem/utils/files.py +323 -0
  216. rem/utils/markdown.py +16 -0
  217. rem/utils/mime_types.py +158 -0
  218. rem/utils/model_helpers.py +492 -0
  219. rem/utils/schema_loader.py +649 -0
  220. rem/utils/sql_paths.py +146 -0
  221. rem/utils/sql_types.py +350 -0
  222. rem/utils/user_id.py +81 -0
  223. rem/utils/vision.py +325 -0
  224. rem/workers/README.md +506 -0
  225. rem/workers/__init__.py +7 -0
  226. rem/workers/db_listener.py +579 -0
  227. rem/workers/db_maintainer.py +74 -0
  228. rem/workers/dreaming.py +502 -0
  229. rem/workers/engram_processor.py +312 -0
  230. rem/workers/sqs_file_processor.py +193 -0
  231. rem/workers/unlogged_maintainer.py +463 -0
  232. remdb-0.3.242.dist-info/METADATA +1632 -0
  233. remdb-0.3.242.dist-info/RECORD +235 -0
  234. remdb-0.3.242.dist-info/WHEEL +4 -0
  235. remdb-0.3.242.dist-info/entry_points.txt +2 -0
@@ -0,0 +1,360 @@
1
+ """
2
+ Email Templates.
3
+
4
+ HTML email templates for transactional emails.
5
+ Uses inline CSS for maximum email client compatibility.
6
+
7
+ Downstream apps can customize by:
8
+ 1. Overriding COLORS dict
9
+ 2. Setting custom LOGO_URL and TAGLINE
10
+ 3. Creating custom templates using base_template()
11
+ """
12
+
13
+ from dataclasses import dataclass
14
+ from typing import Optional
15
+
16
+
17
+ # Default colors - override in downstream apps
18
+ COLORS = {
19
+ "background": "#F5F5F5",
20
+ "foreground": "#333333",
21
+ "primary": "#4A90D9",
22
+ "accent": "#5CB85C",
23
+ "card": "#FFFFFF",
24
+ "muted": "#6b7280",
25
+ "border": "#E0E0E0",
26
+ }
27
+
28
+ # Branding - override in downstream apps
29
+ LOGO_URL: str | None = None
30
+ APP_NAME = "REM"
31
+ TAGLINE = "Your AI-powered platform"
32
+ WEBSITE_URL = "https://rem.ai"
33
+ PRIVACY_URL = "https://rem.ai/privacy"
34
+ TERMS_URL = "https://rem.ai/terms"
35
+
36
+
37
+ @dataclass
38
+ class EmailTemplate:
39
+ """Email template with subject and HTML body."""
40
+
41
+ subject: str
42
+ html_body: str
43
+
44
+
45
+ def base_template(
46
+ content: str,
47
+ preheader: Optional[str] = None,
48
+ colors: dict | None = None,
49
+ logo_url: str | None = None,
50
+ app_name: str | None = None,
51
+ tagline: str | None = None,
52
+ website_url: str | None = None,
53
+ privacy_url: str | None = None,
54
+ terms_url: str | None = None,
55
+ ) -> str:
56
+ """
57
+ Base email template.
58
+
59
+ Args:
60
+ content: The main content HTML
61
+ preheader: Optional preview text shown in email clients
62
+ colors: Color overrides (merges with COLORS)
63
+ logo_url: Logo image URL
64
+ app_name: Application name
65
+ tagline: Footer tagline
66
+ website_url: Main website URL
67
+ privacy_url: Privacy policy URL
68
+ terms_url: Terms of service URL
69
+
70
+ Returns:
71
+ Complete HTML email
72
+ """
73
+ # Merge colors
74
+ c = {**COLORS, **(colors or {})}
75
+
76
+ # Use provided values or module defaults
77
+ logo = logo_url or LOGO_URL
78
+ name = app_name or APP_NAME
79
+ tag = tagline or TAGLINE
80
+ web = website_url or WEBSITE_URL
81
+ privacy = privacy_url or PRIVACY_URL
82
+ terms = terms_url or TERMS_URL
83
+
84
+ preheader_html = ""
85
+ if preheader:
86
+ preheader_html = f'''
87
+ <div style="display: none; max-height: 0; overflow: hidden;">
88
+ {preheader}
89
+ </div>
90
+ '''
91
+
92
+ logo_html = ""
93
+ if logo:
94
+ logo_html = f'''
95
+ <img src="{logo}" alt="{name}" width="40" height="40" style="display: block; margin: 0 auto 16px auto; border-radius: 8px;">
96
+ '''
97
+
98
+ return f'''<!DOCTYPE html>
99
+ <html lang="en">
100
+ <head>
101
+ <meta charset="UTF-8">
102
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
103
+ <meta http-equiv="X-UA-Compatible" content="IE=edge">
104
+ <title>{name}</title>
105
+ <!--[if mso]>
106
+ <style type="text/css">
107
+ body, table, td {{font-family: Arial, sans-serif !important;}}
108
+ </style>
109
+ <![endif]-->
110
+ </head>
111
+ <body style="margin: 0; padding: 0; background-color: {c['background']}; font-family: 'Georgia', 'Times New Roman', serif;">
112
+ {preheader_html}
113
+
114
+ <!-- Email Container -->
115
+ <table role="presentation" cellspacing="0" cellpadding="0" border="0" width="100%" style="background-color: {c['background']};">
116
+ <tr>
117
+ <td style="padding: 40px 20px;">
118
+ <!-- Inner Container -->
119
+ <table role="presentation" cellspacing="0" cellpadding="0" border="0" width="100%" style="max-width: 560px; margin: 0 auto;">
120
+
121
+ <!-- Main Content Card -->
122
+ <tr>
123
+ <td style="background-color: {c['card']}; border-radius: 16px; box-shadow: 0 4px 6px rgba(0, 0, 0, 0.05);">
124
+ <table role="presentation" cellspacing="0" cellpadding="0" border="0" width="100%">
125
+ <tr>
126
+ <td style="padding: 40px;">
127
+ {content}
128
+ </td>
129
+ </tr>
130
+ </table>
131
+ </td>
132
+ </tr>
133
+
134
+ <!-- Footer -->
135
+ <tr>
136
+ <td style="padding: 32px 20px; text-align: center;">
137
+ {logo_html}
138
+
139
+ <!-- Tagline -->
140
+ <p style="margin: 0 0 8px 0; font-family: 'Georgia', serif; font-size: 14px; color: {c['muted']}; font-style: italic;">
141
+ {tag}
142
+ </p>
143
+
144
+ <!-- Footer Links -->
145
+ <p style="margin: 0; font-family: Arial, sans-serif; font-size: 12px; color: {c['muted']};">
146
+ <a href="{web}" style="color: {c['primary']}; text-decoration: none;">{name}</a>
147
+ &nbsp;&bull;&nbsp;
148
+ <a href="{privacy}" style="color: {c['primary']}; text-decoration: none;">Privacy</a>
149
+ &nbsp;&bull;&nbsp;
150
+ <a href="{terms}" style="color: {c['primary']}; text-decoration: none;">Terms</a>
151
+ </p>
152
+
153
+ <!-- Copyright -->
154
+ <p style="margin: 16px 0 0 0; font-family: Arial, sans-serif; font-size: 11px; color: {c['muted']};">
155
+ &copy; 2025 {name}. All rights reserved.
156
+ </p>
157
+ </td>
158
+ </tr>
159
+
160
+ </table>
161
+ </td>
162
+ </tr>
163
+ </table>
164
+ </body>
165
+ </html>'''
166
+
167
+
168
+ def login_code_template(
169
+ code: str,
170
+ email: str,
171
+ colors: dict | None = None,
172
+ app_name: str | None = None,
173
+ **kwargs,
174
+ ) -> EmailTemplate:
175
+ """
176
+ Generate a login code email template.
177
+
178
+ Args:
179
+ code: The 6-digit login code
180
+ email: The recipient's email address
181
+ colors: Color overrides
182
+ app_name: Application name
183
+ **kwargs: Additional arguments passed to base_template
184
+
185
+ Returns:
186
+ EmailTemplate with subject and HTML body
187
+ """
188
+ c = {**COLORS, **(colors or {})}
189
+ name = app_name or APP_NAME
190
+
191
+ # Format code with spaces for readability (e.g., "123 456")
192
+ formatted_code = f"{code[:3]} {code[3:]}" if len(code) == 6 else code
193
+
194
+ content = f'''
195
+ <!-- Greeting -->
196
+ <h1 style="margin: 0 0 8px 0; font-family: 'Arial', sans-serif; font-size: 24px; font-weight: 600; color: {c['foreground']};">
197
+ Hi there!
198
+ </h1>
199
+
200
+ <p style="margin: 0 0 24px 0; font-family: 'Georgia', serif; font-size: 16px; line-height: 1.6; color: {c['foreground']};">
201
+ Here's your login code for {name}. Enter this code to securely access your account.
202
+ </p>
203
+
204
+ <!-- Code Box -->
205
+ <table role="presentation" cellspacing="0" cellpadding="0" border="0" width="100%" style="margin: 0 0 24px 0;">
206
+ <tr>
207
+ <td align="center">
208
+ <div style="
209
+ display: inline-block;
210
+ padding: 20px 40px;
211
+ background-color: {c['background']};
212
+ border: 2px solid {c['border']};
213
+ border-radius: 12px;
214
+ ">
215
+ <span style="
216
+ font-family: 'Courier New', monospace;
217
+ font-size: 32px;
218
+ font-weight: 700;
219
+ letter-spacing: 6px;
220
+ color: {c['foreground']};
221
+ ">{formatted_code}</span>
222
+ </div>
223
+ </td>
224
+ </tr>
225
+ </table>
226
+
227
+ <!-- Instructions -->
228
+ <p style="margin: 0 0 8px 0; font-family: 'Georgia', serif; font-size: 14px; line-height: 1.6; color: {c['muted']};">
229
+ This code expires in <strong>10 minutes</strong>.
230
+ </p>
231
+
232
+ <p style="margin: 0 0 24px 0; font-family: 'Georgia', serif; font-size: 14px; line-height: 1.6; color: {c['muted']};">
233
+ If you didn't request this code, you can safely ignore this email.
234
+ </p>
235
+
236
+ <!-- Divider -->
237
+ <hr style="border: none; border-top: 1px solid {c['border']}; margin: 24px 0;">
238
+
239
+ <!-- Security Note -->
240
+ <table role="presentation" cellspacing="0" cellpadding="0" border="0" width="100%">
241
+ <tr>
242
+ <td style="padding: 16px; background-color: {c['background']}; border-radius: 8px;">
243
+ <p style="margin: 0; font-family: Arial, sans-serif; font-size: 12px; color: {c['muted']};">
244
+ <strong style="color: {c['primary']};">Security tip:</strong>
245
+ Never share your login code with anyone. {name} will never ask for your code via phone or text.
246
+ </p>
247
+ </td>
248
+ </tr>
249
+ </table>
250
+ '''
251
+
252
+ return EmailTemplate(
253
+ subject=f"Your {name} Login Code",
254
+ html_body=base_template(
255
+ content=content,
256
+ preheader=f"Your login code is {formatted_code}",
257
+ colors=colors,
258
+ app_name=app_name,
259
+ **kwargs,
260
+ ),
261
+ )
262
+
263
+
264
+ def welcome_template(
265
+ name: Optional[str] = None,
266
+ colors: dict | None = None,
267
+ app_name: str | None = None,
268
+ features: list[str] | None = None,
269
+ cta_url: str | None = None,
270
+ cta_text: str = "Get Started",
271
+ **kwargs,
272
+ ) -> EmailTemplate:
273
+ """
274
+ Generate a welcome email template for new users.
275
+
276
+ Args:
277
+ name: Optional user's name
278
+ colors: Color overrides
279
+ app_name: Application name
280
+ features: List of feature descriptions
281
+ cta_url: Call-to-action button URL
282
+ cta_text: Call-to-action button text
283
+ **kwargs: Additional arguments passed to base_template
284
+
285
+ Returns:
286
+ EmailTemplate with subject and HTML body
287
+ """
288
+ c = {**COLORS, **(colors or {})}
289
+ app = app_name or APP_NAME
290
+ greeting = f"Hi {name}!" if name else "Welcome!"
291
+ url = cta_url or WEBSITE_URL
292
+
293
+ # Default features if not provided
294
+ default_features = [
295
+ "Access powerful AI capabilities",
296
+ "Store and organize your data",
297
+ "Collaborate with your team",
298
+ ]
299
+ feature_list = features or default_features
300
+
301
+ features_html = "".join(f"<li>{f}</li>" for f in feature_list)
302
+
303
+ content = f'''
304
+ <!-- Greeting -->
305
+ <h1 style="margin: 0 0 8px 0; font-family: 'Arial', sans-serif; font-size: 24px; font-weight: 600; color: {c['foreground']};">
306
+ {greeting}
307
+ </h1>
308
+
309
+ <p style="margin: 0 0 24px 0; font-family: 'Georgia', serif; font-size: 16px; line-height: 1.6; color: {c['foreground']};">
310
+ Welcome to {app}! We're excited to have you on board.
311
+ </p>
312
+
313
+ <!-- Feature List -->
314
+ <table role="presentation" cellspacing="0" cellpadding="0" border="0" width="100%" style="margin: 0 0 24px 0;">
315
+ <tr>
316
+ <td style="padding: 16px; background-color: {c['background']}; border-radius: 8px;">
317
+ <p style="margin: 0 0 12px 0; font-family: Arial, sans-serif; font-size: 14px; font-weight: 600; color: {c['primary']};">
318
+ What you can do with {app}:
319
+ </p>
320
+ <ul style="margin: 0; padding-left: 20px; font-family: 'Georgia', serif; font-size: 14px; line-height: 1.8; color: {c['foreground']};">
321
+ {features_html}
322
+ </ul>
323
+ </td>
324
+ </tr>
325
+ </table>
326
+
327
+ <!-- CTA Button -->
328
+ <table role="presentation" cellspacing="0" cellpadding="0" border="0" width="100%" style="margin: 0 0 24px 0;">
329
+ <tr>
330
+ <td align="center">
331
+ <a href="{url}" style="
332
+ display: inline-block;
333
+ padding: 14px 32px;
334
+ background-color: {c['primary']};
335
+ color: #FFFFFF;
336
+ font-family: Arial, sans-serif;
337
+ font-size: 16px;
338
+ font-weight: 600;
339
+ text-decoration: none;
340
+ border-radius: 8px;
341
+ ">{cta_text}</a>
342
+ </td>
343
+ </tr>
344
+ </table>
345
+
346
+ <p style="margin: 0; font-family: 'Georgia', serif; font-size: 14px; line-height: 1.6; color: {c['muted']}; text-align: center;">
347
+ We're glad you're here!
348
+ </p>
349
+ '''
350
+
351
+ return EmailTemplate(
352
+ subject=f"Welcome to {app}",
353
+ html_body=base_template(
354
+ content=content,
355
+ preheader=f"Welcome to {app}! Get started today.",
356
+ colors=colors,
357
+ app_name=app_name,
358
+ **kwargs,
359
+ ),
360
+ )
@@ -0,0 +1,11 @@
1
+ """Embeddings service for background embedding generation."""
2
+
3
+ from .api import generate_embedding, generate_embedding_async
4
+ from .worker import EmbeddingTask, EmbeddingWorker
5
+
6
+ __all__ = [
7
+ "EmbeddingTask",
8
+ "EmbeddingWorker",
9
+ "generate_embedding",
10
+ "generate_embedding_async",
11
+ ]
@@ -0,0 +1,127 @@
1
+ """
2
+ Embedding API utilities for generating embeddings from text.
3
+
4
+ Provides synchronous and async wrappers for embedding generation using
5
+ raw HTTP requests (no OpenAI SDK dependency).
6
+ """
7
+
8
+ from typing import Optional, cast
9
+
10
+ import httpx
11
+ import requests
12
+ from loguru import logger
13
+
14
+ from rem.utils.constants import DEFAULT_EMBEDDING_DIMS, HTTP_TIMEOUT_DEFAULT
15
+
16
+
17
+ def _get_openai_api_key() -> Optional[str]:
18
+ """Get OpenAI API key from settings."""
19
+ from rem.settings import settings
20
+ return settings.llm.openai_api_key
21
+
22
+
23
+ def generate_embedding(
24
+ text: str,
25
+ model: str = "text-embedding-3-small",
26
+ provider: str = "openai",
27
+ api_key: Optional[str] = None,
28
+ ) -> list[float]:
29
+ """
30
+ Generate embedding for a single text string using requests.
31
+
32
+ Args:
33
+ text: Text to embed
34
+ model: Model name (default: text-embedding-3-small)
35
+ provider: Provider name (default: openai)
36
+ api_key: API key (defaults to settings.llm.openai_api_key)
37
+
38
+ Returns:
39
+ Embedding vector (1536 dimensions for text-embedding-3-small)
40
+ """
41
+ if provider == "openai":
42
+ api_key = api_key or _get_openai_api_key()
43
+ if not api_key:
44
+ logger.warning("No OpenAI API key - returning zero vector")
45
+ return [0.0] * DEFAULT_EMBEDDING_DIMS
46
+
47
+ try:
48
+ logger.debug(f"Generating OpenAI embedding for text using {model}")
49
+
50
+ response = requests.post(
51
+ "https://api.openai.com/v1/embeddings",
52
+ headers={
53
+ "Authorization": f"Bearer {api_key}",
54
+ "Content-Type": "application/json",
55
+ },
56
+ json={"input": [text], "model": model},
57
+ timeout=HTTP_TIMEOUT_DEFAULT,
58
+ )
59
+ response.raise_for_status()
60
+
61
+ data = response.json()
62
+ embedding = data["data"][0]["embedding"]
63
+ logger.debug(f"Successfully generated embedding (dimension: {len(embedding)})")
64
+ return cast(list[float], embedding)
65
+
66
+ except Exception as e:
67
+ logger.error(f"Failed to generate embedding from OpenAI: {e}", exc_info=True)
68
+ return [0.0] * DEFAULT_EMBEDDING_DIMS
69
+
70
+ else:
71
+ logger.warning(f"Unsupported provider '{provider}' - returning zero vector")
72
+ return [0.0] * DEFAULT_EMBEDDING_DIMS
73
+
74
+
75
+ async def generate_embedding_async(
76
+ text: str,
77
+ model: str = "text-embedding-3-small",
78
+ provider: str = "openai",
79
+ api_key: Optional[str] = None,
80
+ ) -> list[float]:
81
+ """
82
+ Generate embedding for a single text string (async version) using httpx.
83
+
84
+ Args:
85
+ text: Text to embed
86
+ model: Model name (default: text-embedding-3-small)
87
+ provider: Provider name (default: openai)
88
+ api_key: API key (defaults to settings.llm.openai_api_key)
89
+
90
+ Returns:
91
+ Embedding vector (1536 dimensions for text-embedding-3-small)
92
+ """
93
+ if provider == "openai":
94
+ api_key = api_key or _get_openai_api_key()
95
+ if not api_key:
96
+ logger.warning("No OpenAI API key - returning zero vector")
97
+ return [0.0] * DEFAULT_EMBEDDING_DIMS
98
+
99
+ try:
100
+ logger.debug(f"Generating OpenAI embedding for text using {model}")
101
+
102
+ async with httpx.AsyncClient() as client:
103
+ response = await client.post(
104
+ "https://api.openai.com/v1/embeddings",
105
+ headers={
106
+ "Authorization": f"Bearer {api_key}",
107
+ "Content-Type": "application/json",
108
+ },
109
+ json={"input": [text], "model": model},
110
+ timeout=HTTP_TIMEOUT_DEFAULT,
111
+ )
112
+ response.raise_for_status()
113
+
114
+ data = response.json()
115
+ embedding = data["data"][0]["embedding"]
116
+ logger.debug(
117
+ f"Successfully generated embedding (dimension: {len(embedding)})"
118
+ )
119
+ return cast(list[float], embedding)
120
+
121
+ except Exception as e:
122
+ logger.error(f"Failed to generate embedding from OpenAI: {e}", exc_info=True)
123
+ return [0.0] * DEFAULT_EMBEDDING_DIMS
124
+
125
+ else:
126
+ logger.warning(f"Unsupported provider '{provider}' - returning zero vector")
127
+ return [0.0] * DEFAULT_EMBEDDING_DIMS