khoj 1.33.3.dev32__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 (393) hide show
  1. khoj/__init__.py +0 -0
  2. khoj/app/README.md +94 -0
  3. khoj/app/__init__.py +0 -0
  4. khoj/app/asgi.py +16 -0
  5. khoj/app/settings.py +218 -0
  6. khoj/app/urls.py +25 -0
  7. khoj/configure.py +452 -0
  8. khoj/database/__init__.py +0 -0
  9. khoj/database/adapters/__init__.py +1821 -0
  10. khoj/database/admin.py +417 -0
  11. khoj/database/apps.py +6 -0
  12. khoj/database/management/__init__.py +0 -0
  13. khoj/database/management/commands/__init__.py +0 -0
  14. khoj/database/management/commands/change_default_model.py +116 -0
  15. khoj/database/management/commands/change_generated_images_url.py +61 -0
  16. khoj/database/management/commands/convert_images_png_to_webp.py +99 -0
  17. khoj/database/migrations/0001_khojuser.py +98 -0
  18. khoj/database/migrations/0002_googleuser.py +32 -0
  19. khoj/database/migrations/0003_vector_extension.py +10 -0
  20. khoj/database/migrations/0004_content_types_and_more.py +181 -0
  21. khoj/database/migrations/0005_embeddings_corpus_id.py +19 -0
  22. khoj/database/migrations/0006_embeddingsdates.py +33 -0
  23. khoj/database/migrations/0007_add_conversation.py +27 -0
  24. khoj/database/migrations/0008_alter_conversation_conversation_log.py +17 -0
  25. khoj/database/migrations/0009_khojapiuser.py +24 -0
  26. khoj/database/migrations/0010_chatmodeloptions_and_more.py +83 -0
  27. khoj/database/migrations/0010_rename_embeddings_entry_and_more.py +30 -0
  28. khoj/database/migrations/0011_merge_20231102_0138.py +14 -0
  29. khoj/database/migrations/0012_entry_file_source.py +21 -0
  30. khoj/database/migrations/0013_subscription.py +37 -0
  31. khoj/database/migrations/0014_alter_googleuser_picture.py +17 -0
  32. khoj/database/migrations/0015_alter_subscription_user.py +21 -0
  33. khoj/database/migrations/0016_alter_subscription_renewal_date.py +17 -0
  34. khoj/database/migrations/0017_searchmodel.py +32 -0
  35. khoj/database/migrations/0018_searchmodelconfig_delete_searchmodel.py +30 -0
  36. khoj/database/migrations/0019_alter_googleuser_family_name_and_more.py +27 -0
  37. khoj/database/migrations/0020_reflectivequestion.py +36 -0
  38. khoj/database/migrations/0021_speechtotextmodeloptions_and_more.py +42 -0
  39. khoj/database/migrations/0022_texttoimagemodelconfig.py +25 -0
  40. khoj/database/migrations/0023_usersearchmodelconfig.py +33 -0
  41. khoj/database/migrations/0024_alter_entry_embeddings.py +18 -0
  42. khoj/database/migrations/0025_clientapplication_khojuser_phone_number_and_more.py +46 -0
  43. khoj/database/migrations/0025_searchmodelconfig_embeddings_inference_endpoint_and_more.py +22 -0
  44. khoj/database/migrations/0026_searchmodelconfig_cross_encoder_inference_endpoint_and_more.py +22 -0
  45. khoj/database/migrations/0027_merge_20240118_1324.py +13 -0
  46. khoj/database/migrations/0028_khojuser_verified_phone_number.py +17 -0
  47. khoj/database/migrations/0029_userrequests.py +27 -0
  48. khoj/database/migrations/0030_conversation_slug_and_title.py +38 -0
  49. khoj/database/migrations/0031_agent_conversation_agent.py +53 -0
  50. khoj/database/migrations/0031_alter_googleuser_locale.py +30 -0
  51. khoj/database/migrations/0032_merge_20240322_0427.py +14 -0
  52. khoj/database/migrations/0033_rename_tuning_agent_personality.py +17 -0
  53. khoj/database/migrations/0034_alter_chatmodeloptions_chat_model.py +32 -0
  54. khoj/database/migrations/0035_processlock.py +26 -0
  55. khoj/database/migrations/0036_alter_processlock_name.py +19 -0
  56. khoj/database/migrations/0036_delete_offlinechatprocessorconversationconfig.py +15 -0
  57. khoj/database/migrations/0036_publicconversation.py +42 -0
  58. khoj/database/migrations/0037_chatmodeloptions_openai_config_and_more.py +51 -0
  59. khoj/database/migrations/0037_searchmodelconfig_bi_encoder_docs_encode_config_and_more.py +32 -0
  60. khoj/database/migrations/0038_merge_20240425_0857.py +14 -0
  61. khoj/database/migrations/0038_merge_20240426_1640.py +12 -0
  62. khoj/database/migrations/0039_merge_20240501_0301.py +12 -0
  63. khoj/database/migrations/0040_alter_processlock_name.py +26 -0
  64. khoj/database/migrations/0040_merge_20240504_1010.py +14 -0
  65. khoj/database/migrations/0041_merge_20240505_1234.py +14 -0
  66. khoj/database/migrations/0042_serverchatsettings.py +46 -0
  67. khoj/database/migrations/0043_alter_chatmodeloptions_model_type.py +21 -0
  68. khoj/database/migrations/0044_conversation_file_filters.py +17 -0
  69. khoj/database/migrations/0045_fileobject.py +37 -0
  70. khoj/database/migrations/0046_khojuser_email_verification_code_and_more.py +22 -0
  71. khoj/database/migrations/0047_alter_entry_file_type.py +31 -0
  72. khoj/database/migrations/0048_voicemodeloption_uservoicemodelconfig.py +52 -0
  73. khoj/database/migrations/0049_datastore.py +38 -0
  74. khoj/database/migrations/0049_texttoimagemodelconfig_api_key_and_more.py +58 -0
  75. khoj/database/migrations/0050_alter_processlock_name.py +25 -0
  76. khoj/database/migrations/0051_merge_20240702_1220.py +14 -0
  77. khoj/database/migrations/0052_alter_searchmodelconfig_bi_encoder_docs_encode_config_and_more.py +27 -0
  78. khoj/database/migrations/0053_agent_style_color_agent_style_icon.py +61 -0
  79. khoj/database/migrations/0054_alter_agent_style_color.py +38 -0
  80. khoj/database/migrations/0055_alter_agent_style_icon.py +37 -0
  81. khoj/database/migrations/0056_chatmodeloptions_vision_enabled.py +17 -0
  82. khoj/database/migrations/0056_searchmodelconfig_cross_encoder_model_config.py +17 -0
  83. khoj/database/migrations/0057_merge_20240816_1409.py +13 -0
  84. khoj/database/migrations/0057_remove_serverchatsettings_default_model_and_more.py +51 -0
  85. khoj/database/migrations/0058_alter_chatmodeloptions_chat_model.py +17 -0
  86. khoj/database/migrations/0059_searchmodelconfig_bi_encoder_confidence_threshold.py +17 -0
  87. khoj/database/migrations/0060_merge_20240905_1828.py +14 -0
  88. khoj/database/migrations/0061_alter_chatmodeloptions_model_type.py +26 -0
  89. khoj/database/migrations/0061_alter_texttoimagemodelconfig_model_type.py +21 -0
  90. khoj/database/migrations/0062_merge_20240913_0222.py +14 -0
  91. khoj/database/migrations/0063_conversation_temp_id.py +36 -0
  92. khoj/database/migrations/0064_remove_conversation_temp_id_alter_conversation_id.py +86 -0
  93. khoj/database/migrations/0065_remove_agent_avatar_remove_agent_public_and_more.py +49 -0
  94. khoj/database/migrations/0066_remove_agent_tools_agent_input_tools_and_more.py +69 -0
  95. khoj/database/migrations/0067_alter_agent_style_icon.py +50 -0
  96. khoj/database/migrations/0068_alter_agent_output_modes.py +24 -0
  97. khoj/database/migrations/0069_webscraper_serverchatsettings_web_scraper.py +89 -0
  98. khoj/database/migrations/0070_alter_agent_input_tools_alter_agent_output_modes.py +46 -0
  99. khoj/database/migrations/0071_subscription_enabled_trial_at_and_more.py +32 -0
  100. khoj/database/migrations/0072_entry_search_model.py +24 -0
  101. khoj/database/migrations/0073_delete_usersearchmodelconfig.py +15 -0
  102. khoj/database/migrations/0074_alter_conversation_title.py +17 -0
  103. khoj/database/migrations/0075_migrate_generated_assets_and_validate.py +85 -0
  104. khoj/database/migrations/0076_rename_openaiprocessorconversationconfig_aimodelapi_and_more.py +26 -0
  105. khoj/database/migrations/0077_chatmodel_alter_agent_chat_model_and_more.py +62 -0
  106. khoj/database/migrations/0078_khojuser_email_verification_code_expiry.py +17 -0
  107. khoj/database/migrations/__init__.py +0 -0
  108. khoj/database/models/__init__.py +725 -0
  109. khoj/database/tests.py +3 -0
  110. khoj/interface/compiled/404/index.html +1 -0
  111. khoj/interface/compiled/_next/static/Tg-vU1p1B-YKT5Qv8KSHt/_buildManifest.js +1 -0
  112. khoj/interface/compiled/_next/static/Tg-vU1p1B-YKT5Qv8KSHt/_ssgManifest.js +1 -0
  113. khoj/interface/compiled/_next/static/chunks/1010-8f39bb4648b5ba10.js +1 -0
  114. khoj/interface/compiled/_next/static/chunks/182-f1c48a203dc91e0e.js +20 -0
  115. khoj/interface/compiled/_next/static/chunks/1915-d3c36ad6ce697ce7.js +1 -0
  116. khoj/interface/compiled/_next/static/chunks/2117-165ef4747a5b836b.js +2 -0
  117. khoj/interface/compiled/_next/static/chunks/2581-455000f8aeb08fc3.js +1 -0
  118. khoj/interface/compiled/_next/static/chunks/3727.dcea8f2193111552.js +1 -0
  119. khoj/interface/compiled/_next/static/chunks/3789-a09e37a819171a9d.js +1 -0
  120. khoj/interface/compiled/_next/static/chunks/4124-6c28322ce218d2d5.js +1 -0
  121. khoj/interface/compiled/_next/static/chunks/5427-b52d95253e692bfa.js +1 -0
  122. khoj/interface/compiled/_next/static/chunks/5473-b1cf56dedac6577a.js +1 -0
  123. khoj/interface/compiled/_next/static/chunks/5477-0bbddb79c25a54a7.js +1 -0
  124. khoj/interface/compiled/_next/static/chunks/6065-64db9ad305ba0bcd.js +1 -0
  125. khoj/interface/compiled/_next/static/chunks/6293-469dd16402ea8a6f.js +3 -0
  126. khoj/interface/compiled/_next/static/chunks/688-b5b4391bbc0376f1.js +1 -0
  127. khoj/interface/compiled/_next/static/chunks/8667-b6bf63c72b2d76eb.js +1 -0
  128. khoj/interface/compiled/_next/static/chunks/9259-1172dbaca0515237.js +1 -0
  129. khoj/interface/compiled/_next/static/chunks/94ca1967.1d9b42d929a1ee8c.js +1 -0
  130. khoj/interface/compiled/_next/static/chunks/9597.83583248dfbf6e73.js +1 -0
  131. khoj/interface/compiled/_next/static/chunks/964ecbae.51d6faf8801d15e6.js +1 -0
  132. khoj/interface/compiled/_next/static/chunks/9665-391df1e5c51c960a.js +1 -0
  133. khoj/interface/compiled/_next/static/chunks/app/_not-found/page-a834eddae3e235df.js +1 -0
  134. khoj/interface/compiled/_next/static/chunks/app/agents/layout-e00fb81dca656a10.js +1 -0
  135. khoj/interface/compiled/_next/static/chunks/app/agents/page-28ce086a1129bca2.js +1 -0
  136. khoj/interface/compiled/_next/static/chunks/app/automations/layout-1fe1537449f43496.js +1 -0
  137. khoj/interface/compiled/_next/static/chunks/app/automations/page-bf365a60829d347f.js +1 -0
  138. khoj/interface/compiled/_next/static/chunks/app/chat/layout-33934fc2d6ae6838.js +1 -0
  139. khoj/interface/compiled/_next/static/chunks/app/chat/page-0e476e57eb2015e3.js +1 -0
  140. khoj/interface/compiled/_next/static/chunks/app/layout-30e7fda7262713ce.js +1 -0
  141. khoj/interface/compiled/_next/static/chunks/app/page-a5515ea71aec5ef0.js +1 -0
  142. khoj/interface/compiled/_next/static/chunks/app/search/layout-c02531d586972d7d.js +1 -0
  143. khoj/interface/compiled/_next/static/chunks/app/search/page-9140541e67ea307d.js +1 -0
  144. khoj/interface/compiled/_next/static/chunks/app/settings/layout-d09d6510a45cd4bd.js +1 -0
  145. khoj/interface/compiled/_next/static/chunks/app/settings/page-951ba40b5b94b23a.js +1 -0
  146. khoj/interface/compiled/_next/static/chunks/app/share/chat/layout-e8e5db7830bf3f47.js +1 -0
  147. khoj/interface/compiled/_next/static/chunks/app/share/chat/page-1beb80d8d741c932.js +1 -0
  148. khoj/interface/compiled/_next/static/chunks/d3ac728e-44ebd2a0c99b12a0.js +1 -0
  149. khoj/interface/compiled/_next/static/chunks/fd9d1056-4482b99a36fd1673.js +1 -0
  150. khoj/interface/compiled/_next/static/chunks/framework-8e0e0f4a6b83a956.js +1 -0
  151. khoj/interface/compiled/_next/static/chunks/main-app-de1f09df97a3cfc7.js +1 -0
  152. khoj/interface/compiled/_next/static/chunks/main-db4bfac6b0a8d00b.js +1 -0
  153. khoj/interface/compiled/_next/static/chunks/pages/_app-3c9ca398d360b709.js +1 -0
  154. khoj/interface/compiled/_next/static/chunks/pages/_error-cf5ca766ac8f493f.js +1 -0
  155. khoj/interface/compiled/_next/static/chunks/polyfills-42372ed130431b0a.js +1 -0
  156. khoj/interface/compiled/_next/static/chunks/webpack-a03962458328b163.js +1 -0
  157. khoj/interface/compiled/_next/static/css/089de1d8526b96e9.css +1 -0
  158. khoj/interface/compiled/_next/static/css/37a73b87f02df402.css +1 -0
  159. khoj/interface/compiled/_next/static/css/4e4e6a4a1c920d06.css +1 -0
  160. khoj/interface/compiled/_next/static/css/8d02837c730f8d13.css +25 -0
  161. khoj/interface/compiled/_next/static/css/8e6a3ca11a60b189.css +1 -0
  162. khoj/interface/compiled/_next/static/css/9c164d9727dd8092.css +1 -0
  163. khoj/interface/compiled/_next/static/css/dac88c17aaee5fcf.css +1 -0
  164. khoj/interface/compiled/_next/static/css/df4b47a2d0d85eae.css +1 -0
  165. khoj/interface/compiled/_next/static/css/e4eb883b5265d372.css +1 -0
  166. khoj/interface/compiled/_next/static/media/1d8a05b60287ae6c-s.p.woff2 +0 -0
  167. khoj/interface/compiled/_next/static/media/6f22fce21a7c433c-s.woff2 +0 -0
  168. khoj/interface/compiled/_next/static/media/77c207b095007c34-s.p.woff2 +0 -0
  169. khoj/interface/compiled/_next/static/media/82ef96de0e8f4d8c-s.p.woff2 +0 -0
  170. khoj/interface/compiled/_next/static/media/KaTeX_AMS-Regular.1608a09b.woff +0 -0
  171. khoj/interface/compiled/_next/static/media/KaTeX_AMS-Regular.4aafdb68.ttf +0 -0
  172. khoj/interface/compiled/_next/static/media/KaTeX_AMS-Regular.a79f1c31.woff2 +0 -0
  173. khoj/interface/compiled/_next/static/media/KaTeX_Caligraphic-Bold.b6770918.woff +0 -0
  174. khoj/interface/compiled/_next/static/media/KaTeX_Caligraphic-Bold.cce5b8ec.ttf +0 -0
  175. khoj/interface/compiled/_next/static/media/KaTeX_Caligraphic-Bold.ec17d132.woff2 +0 -0
  176. khoj/interface/compiled/_next/static/media/KaTeX_Caligraphic-Regular.07ef19e7.ttf +0 -0
  177. khoj/interface/compiled/_next/static/media/KaTeX_Caligraphic-Regular.55fac258.woff2 +0 -0
  178. khoj/interface/compiled/_next/static/media/KaTeX_Caligraphic-Regular.dad44a7f.woff +0 -0
  179. khoj/interface/compiled/_next/static/media/KaTeX_Fraktur-Bold.9f256b85.woff +0 -0
  180. khoj/interface/compiled/_next/static/media/KaTeX_Fraktur-Bold.b18f59e1.ttf +0 -0
  181. khoj/interface/compiled/_next/static/media/KaTeX_Fraktur-Bold.d42a5579.woff2 +0 -0
  182. khoj/interface/compiled/_next/static/media/KaTeX_Fraktur-Regular.7c187121.woff +0 -0
  183. khoj/interface/compiled/_next/static/media/KaTeX_Fraktur-Regular.d3c882a6.woff2 +0 -0
  184. khoj/interface/compiled/_next/static/media/KaTeX_Fraktur-Regular.ed38e79f.ttf +0 -0
  185. khoj/interface/compiled/_next/static/media/KaTeX_Main-Bold.b74a1a8b.ttf +0 -0
  186. khoj/interface/compiled/_next/static/media/KaTeX_Main-Bold.c3fb5ac2.woff2 +0 -0
  187. khoj/interface/compiled/_next/static/media/KaTeX_Main-Bold.d181c465.woff +0 -0
  188. khoj/interface/compiled/_next/static/media/KaTeX_Main-BoldItalic.6f2bb1df.woff2 +0 -0
  189. khoj/interface/compiled/_next/static/media/KaTeX_Main-BoldItalic.70d8b0a5.ttf +0 -0
  190. khoj/interface/compiled/_next/static/media/KaTeX_Main-BoldItalic.e3f82f9d.woff +0 -0
  191. khoj/interface/compiled/_next/static/media/KaTeX_Main-Italic.47373d1e.ttf +0 -0
  192. khoj/interface/compiled/_next/static/media/KaTeX_Main-Italic.8916142b.woff2 +0 -0
  193. khoj/interface/compiled/_next/static/media/KaTeX_Main-Italic.9024d815.woff +0 -0
  194. khoj/interface/compiled/_next/static/media/KaTeX_Main-Regular.0462f03b.woff2 +0 -0
  195. khoj/interface/compiled/_next/static/media/KaTeX_Main-Regular.7f51fe03.woff +0 -0
  196. khoj/interface/compiled/_next/static/media/KaTeX_Main-Regular.b7f8fe9b.ttf +0 -0
  197. khoj/interface/compiled/_next/static/media/KaTeX_Math-BoldItalic.572d331f.woff2 +0 -0
  198. khoj/interface/compiled/_next/static/media/KaTeX_Math-BoldItalic.a879cf83.ttf +0 -0
  199. khoj/interface/compiled/_next/static/media/KaTeX_Math-BoldItalic.f1035d8d.woff +0 -0
  200. khoj/interface/compiled/_next/static/media/KaTeX_Math-Italic.5295ba48.woff +0 -0
  201. khoj/interface/compiled/_next/static/media/KaTeX_Math-Italic.939bc644.ttf +0 -0
  202. khoj/interface/compiled/_next/static/media/KaTeX_Math-Italic.f28c23ac.woff2 +0 -0
  203. khoj/interface/compiled/_next/static/media/KaTeX_SansSerif-Bold.8c5b5494.woff2 +0 -0
  204. khoj/interface/compiled/_next/static/media/KaTeX_SansSerif-Bold.94e1e8dc.ttf +0 -0
  205. khoj/interface/compiled/_next/static/media/KaTeX_SansSerif-Bold.bf59d231.woff +0 -0
  206. khoj/interface/compiled/_next/static/media/KaTeX_SansSerif-Italic.3b1e59b3.woff2 +0 -0
  207. khoj/interface/compiled/_next/static/media/KaTeX_SansSerif-Italic.7c9bc82b.woff +0 -0
  208. khoj/interface/compiled/_next/static/media/KaTeX_SansSerif-Italic.b4c20c84.ttf +0 -0
  209. khoj/interface/compiled/_next/static/media/KaTeX_SansSerif-Regular.74048478.woff +0 -0
  210. khoj/interface/compiled/_next/static/media/KaTeX_SansSerif-Regular.ba21ed5f.woff2 +0 -0
  211. khoj/interface/compiled/_next/static/media/KaTeX_SansSerif-Regular.d4d7ba48.ttf +0 -0
  212. khoj/interface/compiled/_next/static/media/KaTeX_Script-Regular.03e9641d.woff2 +0 -0
  213. khoj/interface/compiled/_next/static/media/KaTeX_Script-Regular.07505710.woff +0 -0
  214. khoj/interface/compiled/_next/static/media/KaTeX_Script-Regular.fe9cbbe1.ttf +0 -0
  215. khoj/interface/compiled/_next/static/media/KaTeX_Size1-Regular.e1e279cb.woff +0 -0
  216. khoj/interface/compiled/_next/static/media/KaTeX_Size1-Regular.eae34984.woff2 +0 -0
  217. khoj/interface/compiled/_next/static/media/KaTeX_Size1-Regular.fabc004a.ttf +0 -0
  218. khoj/interface/compiled/_next/static/media/KaTeX_Size2-Regular.57727022.woff +0 -0
  219. khoj/interface/compiled/_next/static/media/KaTeX_Size2-Regular.5916a24f.woff2 +0 -0
  220. khoj/interface/compiled/_next/static/media/KaTeX_Size2-Regular.d6b476ec.ttf +0 -0
  221. khoj/interface/compiled/_next/static/media/KaTeX_Size3-Regular.9acaf01c.woff +0 -0
  222. khoj/interface/compiled/_next/static/media/KaTeX_Size3-Regular.a144ef58.ttf +0 -0
  223. khoj/interface/compiled/_next/static/media/KaTeX_Size3-Regular.b4230e7e.woff2 +0 -0
  224. khoj/interface/compiled/_next/static/media/KaTeX_Size4-Regular.10d95fd3.woff2 +0 -0
  225. khoj/interface/compiled/_next/static/media/KaTeX_Size4-Regular.7a996c9d.woff +0 -0
  226. khoj/interface/compiled/_next/static/media/KaTeX_Size4-Regular.fbccdabe.ttf +0 -0
  227. khoj/interface/compiled/_next/static/media/KaTeX_Typewriter-Regular.6258592b.woff +0 -0
  228. khoj/interface/compiled/_next/static/media/KaTeX_Typewriter-Regular.a8709e36.woff2 +0 -0
  229. khoj/interface/compiled/_next/static/media/KaTeX_Typewriter-Regular.d97aaf4a.ttf +0 -0
  230. khoj/interface/compiled/_next/static/media/a6ecd16fa044d500-s.p.woff2 +0 -0
  231. khoj/interface/compiled/_next/static/media/bd82c78e5b7b3fe9-s.p.woff2 +0 -0
  232. khoj/interface/compiled/_next/static/media/c32c8052c071fc42-s.woff2 +0 -0
  233. khoj/interface/compiled/_next/static/media/c4250770ab8708b6-s.p.woff2 +0 -0
  234. khoj/interface/compiled/_next/static/media/e098aaaecc9cfbb2-s.p.woff2 +0 -0
  235. khoj/interface/compiled/_next/static/media/flags.3afdda2f.webp +0 -0
  236. khoj/interface/compiled/_next/static/media/flags@2x.5fbe9fc1.webp +0 -0
  237. khoj/interface/compiled/_next/static/media/globe.98e105ca.webp +0 -0
  238. khoj/interface/compiled/_next/static/media/globe@2x.974df6f8.webp +0 -0
  239. khoj/interface/compiled/agents/index.html +1 -0
  240. khoj/interface/compiled/agents/index.txt +7 -0
  241. khoj/interface/compiled/agents.svg +6 -0
  242. khoj/interface/compiled/assets/icons/khoj_lantern.ico +0 -0
  243. khoj/interface/compiled/assets/icons/khoj_lantern.svg +100 -0
  244. khoj/interface/compiled/assets/icons/khoj_lantern_1200x1200.png +0 -0
  245. khoj/interface/compiled/assets/icons/khoj_lantern_128x128.png +0 -0
  246. khoj/interface/compiled/assets/icons/khoj_lantern_128x128_dark.png +0 -0
  247. khoj/interface/compiled/assets/icons/khoj_lantern_256x256.png +0 -0
  248. khoj/interface/compiled/assets/icons/khoj_lantern_512x512.png +0 -0
  249. khoj/interface/compiled/assets/icons/khoj_lantern_logomarktype_1200x630.png +0 -0
  250. khoj/interface/compiled/assets/samples/desktop-browse-draw-sample.png +0 -0
  251. khoj/interface/compiled/assets/samples/desktop-plain-chat-sample.png +0 -0
  252. khoj/interface/compiled/assets/samples/desktop-remember-plan-sample.png +0 -0
  253. khoj/interface/compiled/assets/samples/phone-browse-draw-sample.png +0 -0
  254. khoj/interface/compiled/assets/samples/phone-plain-chat-sample.png +0 -0
  255. khoj/interface/compiled/assets/samples/phone-remember-plan-sample.png +0 -0
  256. khoj/interface/compiled/automation.svg +37 -0
  257. khoj/interface/compiled/automations/index.html +1 -0
  258. khoj/interface/compiled/automations/index.txt +8 -0
  259. khoj/interface/compiled/chat/index.html +1 -0
  260. khoj/interface/compiled/chat/index.txt +7 -0
  261. khoj/interface/compiled/chat.svg +24 -0
  262. khoj/interface/compiled/close.svg +5 -0
  263. khoj/interface/compiled/copy-button-success.svg +6 -0
  264. khoj/interface/compiled/copy-button.svg +5 -0
  265. khoj/interface/compiled/index.html +1 -0
  266. khoj/interface/compiled/index.txt +7 -0
  267. khoj/interface/compiled/khoj.webmanifest +76 -0
  268. khoj/interface/compiled/logo.svg +24 -0
  269. khoj/interface/compiled/search/index.html +1 -0
  270. khoj/interface/compiled/search/index.txt +7 -0
  271. khoj/interface/compiled/send.svg +1 -0
  272. khoj/interface/compiled/settings/index.html +1 -0
  273. khoj/interface/compiled/settings/index.txt +9 -0
  274. khoj/interface/compiled/share/chat/index.html +1 -0
  275. khoj/interface/compiled/share/chat/index.txt +7 -0
  276. khoj/interface/compiled/share.svg +8 -0
  277. khoj/interface/compiled/thumbs-down.svg +6 -0
  278. khoj/interface/compiled/thumbs-up.svg +6 -0
  279. khoj/interface/email/feedback.html +34 -0
  280. khoj/interface/email/magic_link.html +40 -0
  281. khoj/interface/email/task.html +37 -0
  282. khoj/interface/email/welcome.html +90 -0
  283. khoj/interface/web/.well-known/assetlinks.json +11 -0
  284. khoj/interface/web/assets/icons/agents.svg +19 -0
  285. khoj/interface/web/assets/icons/automation.svg +43 -0
  286. khoj/interface/web/assets/icons/chat.svg +24 -0
  287. khoj/interface/web/assets/icons/github.svg +1 -0
  288. khoj/interface/web/assets/icons/khoj-logo-sideways-200.png +0 -0
  289. khoj/interface/web/assets/icons/khoj-logo-sideways-500.png +0 -0
  290. khoj/interface/web/assets/icons/khoj-logo-sideways.svg +32 -0
  291. khoj/interface/web/assets/icons/khoj.svg +26 -0
  292. khoj/interface/web/assets/icons/logotype.svg +1 -0
  293. khoj/interface/web/assets/icons/search.svg +57 -0
  294. khoj/interface/web/assets/icons/sync.svg +4 -0
  295. khoj/interface/web/assets/khoj.css +237 -0
  296. khoj/interface/web/assets/utils.js +33 -0
  297. khoj/interface/web/base_config.html +445 -0
  298. khoj/interface/web/content_source_github_input.html +208 -0
  299. khoj/interface/web/login.html +310 -0
  300. khoj/interface/web/utils.html +48 -0
  301. khoj/main.py +249 -0
  302. khoj/manage.py +22 -0
  303. khoj/migrations/__init__.py +0 -0
  304. khoj/migrations/migrate_offline_chat_default_model.py +69 -0
  305. khoj/migrations/migrate_offline_chat_default_model_2.py +71 -0
  306. khoj/migrations/migrate_offline_chat_schema.py +83 -0
  307. khoj/migrations/migrate_offline_model.py +29 -0
  308. khoj/migrations/migrate_processor_config_openai.py +67 -0
  309. khoj/migrations/migrate_server_pg.py +132 -0
  310. khoj/migrations/migrate_version.py +17 -0
  311. khoj/processor/__init__.py +0 -0
  312. khoj/processor/content/__init__.py +0 -0
  313. khoj/processor/content/docx/__init__.py +0 -0
  314. khoj/processor/content/docx/docx_to_entries.py +111 -0
  315. khoj/processor/content/github/__init__.py +0 -0
  316. khoj/processor/content/github/github_to_entries.py +226 -0
  317. khoj/processor/content/images/__init__.py +0 -0
  318. khoj/processor/content/images/image_to_entries.py +117 -0
  319. khoj/processor/content/markdown/__init__.py +0 -0
  320. khoj/processor/content/markdown/markdown_to_entries.py +160 -0
  321. khoj/processor/content/notion/notion_to_entries.py +259 -0
  322. khoj/processor/content/org_mode/__init__.py +0 -0
  323. khoj/processor/content/org_mode/org_to_entries.py +226 -0
  324. khoj/processor/content/org_mode/orgnode.py +532 -0
  325. khoj/processor/content/pdf/__init__.py +0 -0
  326. khoj/processor/content/pdf/pdf_to_entries.py +119 -0
  327. khoj/processor/content/plaintext/__init__.py +0 -0
  328. khoj/processor/content/plaintext/plaintext_to_entries.py +117 -0
  329. khoj/processor/content/text_to_entries.py +296 -0
  330. khoj/processor/conversation/__init__.py +0 -0
  331. khoj/processor/conversation/anthropic/__init__.py +0 -0
  332. khoj/processor/conversation/anthropic/anthropic_chat.py +243 -0
  333. khoj/processor/conversation/anthropic/utils.py +217 -0
  334. khoj/processor/conversation/google/__init__.py +0 -0
  335. khoj/processor/conversation/google/gemini_chat.py +253 -0
  336. khoj/processor/conversation/google/utils.py +260 -0
  337. khoj/processor/conversation/offline/__init__.py +0 -0
  338. khoj/processor/conversation/offline/chat_model.py +308 -0
  339. khoj/processor/conversation/offline/utils.py +80 -0
  340. khoj/processor/conversation/offline/whisper.py +15 -0
  341. khoj/processor/conversation/openai/__init__.py +0 -0
  342. khoj/processor/conversation/openai/gpt.py +243 -0
  343. khoj/processor/conversation/openai/utils.py +232 -0
  344. khoj/processor/conversation/openai/whisper.py +13 -0
  345. khoj/processor/conversation/prompts.py +1188 -0
  346. khoj/processor/conversation/utils.py +867 -0
  347. khoj/processor/embeddings.py +122 -0
  348. khoj/processor/image/generate.py +215 -0
  349. khoj/processor/speech/__init__.py +0 -0
  350. khoj/processor/speech/text_to_speech.py +51 -0
  351. khoj/processor/tools/__init__.py +0 -0
  352. khoj/processor/tools/online_search.py +472 -0
  353. khoj/processor/tools/run_code.py +179 -0
  354. khoj/routers/__init__.py +0 -0
  355. khoj/routers/api.py +760 -0
  356. khoj/routers/api_agents.py +295 -0
  357. khoj/routers/api_chat.py +1273 -0
  358. khoj/routers/api_content.py +634 -0
  359. khoj/routers/api_model.py +123 -0
  360. khoj/routers/api_phone.py +86 -0
  361. khoj/routers/api_subscription.py +144 -0
  362. khoj/routers/auth.py +307 -0
  363. khoj/routers/email.py +135 -0
  364. khoj/routers/helpers.py +2333 -0
  365. khoj/routers/notion.py +85 -0
  366. khoj/routers/research.py +364 -0
  367. khoj/routers/storage.py +63 -0
  368. khoj/routers/twilio.py +36 -0
  369. khoj/routers/web_client.py +141 -0
  370. khoj/search_filter/__init__.py +0 -0
  371. khoj/search_filter/base_filter.py +15 -0
  372. khoj/search_filter/date_filter.py +215 -0
  373. khoj/search_filter/file_filter.py +32 -0
  374. khoj/search_filter/word_filter.py +29 -0
  375. khoj/search_type/__init__.py +0 -0
  376. khoj/search_type/text_search.py +255 -0
  377. khoj/utils/__init__.py +0 -0
  378. khoj/utils/cli.py +101 -0
  379. khoj/utils/config.py +81 -0
  380. khoj/utils/constants.py +51 -0
  381. khoj/utils/fs_syncer.py +252 -0
  382. khoj/utils/helpers.py +627 -0
  383. khoj/utils/initialization.py +301 -0
  384. khoj/utils/jsonl.py +43 -0
  385. khoj/utils/models.py +47 -0
  386. khoj/utils/rawconfig.py +208 -0
  387. khoj/utils/state.py +48 -0
  388. khoj/utils/yaml.py +47 -0
  389. khoj-1.33.3.dev32.dist-info/METADATA +190 -0
  390. khoj-1.33.3.dev32.dist-info/RECORD +393 -0
  391. khoj-1.33.3.dev32.dist-info/WHEEL +4 -0
  392. khoj-1.33.3.dev32.dist-info/entry_points.txt +2 -0
  393. khoj-1.33.3.dev32.dist-info/licenses/LICENSE +661 -0
@@ -0,0 +1,123 @@
1
+ import json
2
+ import logging
3
+ from typing import Dict, Optional, Union
4
+
5
+ from fastapi import APIRouter, HTTPException, Request
6
+ from fastapi.requests import Request
7
+ from fastapi.responses import Response
8
+ from starlette.authentication import has_required_scope, requires
9
+
10
+ from khoj.database import adapters
11
+ from khoj.database.adapters import ConversationAdapters, EntryAdapters
12
+ from khoj.routers.helpers import update_telemetry_state
13
+
14
+ api_model = APIRouter()
15
+ logger = logging.getLogger(__name__)
16
+
17
+
18
+ @api_model.get("/chat/options", response_model=Dict[str, Union[str, int]])
19
+ def get_chat_model_options(
20
+ request: Request,
21
+ client: Optional[str] = None,
22
+ ):
23
+ conversation_options = ConversationAdapters.get_conversation_processor_options().all()
24
+
25
+ all_conversation_options = list()
26
+ for conversation_option in conversation_options:
27
+ all_conversation_options.append({"chat_model": conversation_option.name, "id": conversation_option.id})
28
+
29
+ return Response(content=json.dumps(all_conversation_options), media_type="application/json", status_code=200)
30
+
31
+
32
+ @api_model.get("/chat")
33
+ @requires(["authenticated"])
34
+ def get_user_chat_model(
35
+ request: Request,
36
+ client: Optional[str] = None,
37
+ ):
38
+ user = request.user.object
39
+
40
+ chat_model = ConversationAdapters.get_chat_model(user)
41
+
42
+ if chat_model is None:
43
+ chat_model = ConversationAdapters.get_default_chat_model(user)
44
+
45
+ return Response(status_code=200, content=json.dumps({"id": chat_model.id, "chat_model": chat_model.name}))
46
+
47
+
48
+ @api_model.post("/chat", status_code=200)
49
+ @requires(["authenticated", "premium"])
50
+ async def update_chat_model(
51
+ request: Request,
52
+ id: str,
53
+ client: Optional[str] = None,
54
+ ):
55
+ user = request.user.object
56
+
57
+ new_config = await ConversationAdapters.aset_user_conversation_processor(user, int(id))
58
+
59
+ update_telemetry_state(
60
+ request=request,
61
+ telemetry_type="api",
62
+ api="set_conversation_chat_model",
63
+ client=client,
64
+ metadata={"processor_conversation_type": "conversation"},
65
+ )
66
+
67
+ if new_config is None:
68
+ return {"status": "error", "message": "Model not found"}
69
+
70
+ return {"status": "ok"}
71
+
72
+
73
+ @api_model.post("/voice", status_code=200)
74
+ @requires(["authenticated", "premium"])
75
+ async def update_voice_model(
76
+ request: Request,
77
+ id: str,
78
+ client: Optional[str] = None,
79
+ ):
80
+ user = request.user.object
81
+
82
+ new_config = await ConversationAdapters.aset_user_voice_model(user, id)
83
+
84
+ update_telemetry_state(
85
+ request=request,
86
+ telemetry_type="api",
87
+ api="set_voice_model",
88
+ client=client,
89
+ )
90
+
91
+ if new_config is None:
92
+ return Response(status_code=404, content=json.dumps({"status": "error", "message": "Model not found"}))
93
+
94
+ return Response(status_code=202, content=json.dumps({"status": "ok"}))
95
+
96
+
97
+ @api_model.post("/paint", status_code=200)
98
+ @requires(["authenticated"])
99
+ async def update_paint_model(
100
+ request: Request,
101
+ id: str,
102
+ client: Optional[str] = None,
103
+ ):
104
+ user = request.user.object
105
+ subscribed = has_required_scope(request, ["premium"])
106
+
107
+ if not subscribed:
108
+ raise HTTPException(status_code=403, detail="User is not subscribed to premium")
109
+
110
+ new_config = await ConversationAdapters.aset_user_text_to_image_model(user, int(id))
111
+
112
+ update_telemetry_state(
113
+ request=request,
114
+ telemetry_type="api",
115
+ api="set_paint_model",
116
+ client=client,
117
+ metadata={"paint_model": new_config.setting.model_name},
118
+ )
119
+
120
+ if new_config is None:
121
+ return {"status": "error", "message": "Model not found"}
122
+
123
+ return {"status": "ok"}
@@ -0,0 +1,86 @@
1
+ import logging
2
+ from typing import Optional
3
+
4
+ from fastapi import APIRouter, Depends, HTTPException, Request
5
+ from starlette.authentication import requires
6
+
7
+ from khoj.database import adapters
8
+ from khoj.database.models import KhojUser
9
+ from khoj.routers.helpers import ApiUserRateLimiter, update_telemetry_state
10
+ from khoj.routers.twilio import create_otp, verify_otp
11
+
12
+ api_phone = APIRouter()
13
+ logger = logging.getLogger(__name__)
14
+
15
+
16
+ @api_phone.post("", status_code=200)
17
+ @requires(["authenticated"])
18
+ async def update_phone_number(
19
+ request: Request,
20
+ phone_number: str,
21
+ client: Optional[str] = None,
22
+ rate_limiter_per_day=Depends(
23
+ ApiUserRateLimiter(requests=5, subscribed_requests=5, window=60 * 60 * 24, slug="update_phone")
24
+ ),
25
+ ):
26
+ user = request.user.object
27
+
28
+ await adapters.aset_user_phone_number(user, phone_number)
29
+ create_otp(user)
30
+
31
+ update_telemetry_state(
32
+ request=request,
33
+ telemetry_type="api",
34
+ api="set_phone_number",
35
+ client=client,
36
+ metadata={"phone_number": phone_number},
37
+ )
38
+
39
+ return {"status": "ok"}
40
+
41
+
42
+ @api_phone.delete("", status_code=200)
43
+ @requires(["authenticated"])
44
+ async def delete_phone_number(
45
+ request: Request,
46
+ client: Optional[str] = None,
47
+ ):
48
+ user = request.user.object
49
+
50
+ await adapters.aremove_phone_number(user)
51
+
52
+ update_telemetry_state(
53
+ request=request,
54
+ telemetry_type="api",
55
+ api="delete_phone_number",
56
+ client=client,
57
+ )
58
+
59
+ return {"status": "ok"}
60
+
61
+
62
+ @api_phone.post("/verify", status_code=200)
63
+ @requires(["authenticated"])
64
+ async def verify_mobile_otp(
65
+ request: Request,
66
+ code: str,
67
+ client: Optional[str] = None,
68
+ rate_limiter_per_day=Depends(
69
+ ApiUserRateLimiter(requests=5, subscribed_requests=5, window=60 * 60 * 24, slug="verify_phone")
70
+ ),
71
+ ):
72
+ user: KhojUser = request.user.object
73
+
74
+ update_telemetry_state(
75
+ request=request,
76
+ telemetry_type="api",
77
+ api="verify_phone_number",
78
+ client=client,
79
+ )
80
+
81
+ if not verify_otp(user, code):
82
+ raise HTTPException(status_code=400, detail="Invalid OTP")
83
+
84
+ user.verified_phone_number = True
85
+ await user.asave()
86
+ return {"status": "ok"}
@@ -0,0 +1,144 @@
1
+ import json
2
+ import logging
3
+ import os
4
+ from datetime import datetime, timezone
5
+
6
+ from asgiref.sync import sync_to_async
7
+ from fastapi import APIRouter, Request, Response
8
+ from starlette.authentication import requires
9
+
10
+ from khoj.database import adapters
11
+ from khoj.database.models import KhojUser, Subscription
12
+ from khoj.routers.helpers import update_telemetry_state
13
+ from khoj.utils import state
14
+
15
+ # Stripe integration for Khoj Cloud Subscription
16
+ if state.billing_enabled:
17
+ import stripe
18
+
19
+ stripe.api_key = os.getenv("STRIPE_API_KEY")
20
+ endpoint_secret = os.getenv("STRIPE_SIGNING_SECRET")
21
+ logger = logging.getLogger(__name__)
22
+ subscription_router = APIRouter()
23
+
24
+
25
+ @subscription_router.post("")
26
+ async def subscribe(request: Request):
27
+ """Webhook for Stripe to send subscription events to Khoj Cloud"""
28
+ event = None
29
+ try:
30
+ payload = await request.body()
31
+ sig_header = request.headers["stripe-signature"]
32
+ event = stripe.Webhook.construct_event(payload, sig_header, endpoint_secret)
33
+ except ValueError as e:
34
+ # Invalid payload
35
+ raise e
36
+ except stripe.error.SignatureVerificationError as e:
37
+ # Invalid signature
38
+ raise e
39
+
40
+ event_type = event["type"]
41
+ if event_type not in {
42
+ "invoice.paid",
43
+ "customer.subscription.updated",
44
+ "customer.subscription.deleted",
45
+ }:
46
+ logger.warning(f"Unhandled Stripe event type: {event['type']}")
47
+ return {"success": False}
48
+
49
+ # Retrieve the customer's details
50
+ subscription = event["data"]["object"]
51
+ customer_id = subscription["customer"]
52
+ customer = stripe.Customer.retrieve(customer_id)
53
+ customer_email = customer["email"]
54
+ user = None
55
+ is_new = False
56
+
57
+ # Handle valid stripe webhook events
58
+ success = True
59
+ if event_type in {"invoice.paid"}:
60
+ # Mark the user as subscribed and update the next renewal date on payment
61
+ subscription = stripe.Subscription.list(customer=customer_id).data[0]
62
+ renewal_date = datetime.fromtimestamp(subscription["current_period_end"], tz=timezone.utc)
63
+ user, is_new = await adapters.set_user_subscription(
64
+ customer_email, is_recurring=True, renewal_date=renewal_date
65
+ )
66
+ success = user is not None
67
+ elif event_type in {"customer.subscription.updated"}:
68
+ user_subscription = await sync_to_async(adapters.get_user_subscription)(customer_email)
69
+
70
+ renewal_date = None
71
+ if subscription["current_period_end"]:
72
+ renewal_date = datetime.fromtimestamp(subscription["current_period_end"], tz=timezone.utc)
73
+
74
+ # Allow updating subscription status if paid user
75
+ if user_subscription and user_subscription.renewal_date:
76
+ # Mark user as unsubscribed or resubscribed
77
+ is_recurring = not subscription["cancel_at_period_end"]
78
+ user, is_new = await adapters.set_user_subscription(
79
+ customer_email, is_recurring=is_recurring, renewal_date=renewal_date
80
+ )
81
+ success = user is not None
82
+ elif event_type in {"customer.subscription.deleted"}:
83
+ # Reset the user to trial state
84
+ user, is_new = await adapters.set_user_subscription(
85
+ customer_email, is_recurring=False, renewal_date=None, type=Subscription.Type.TRIAL
86
+ )
87
+ success = user is not None
88
+
89
+ if user and is_new:
90
+ update_telemetry_state(
91
+ request=request,
92
+ telemetry_type="api",
93
+ api="create_user",
94
+ metadata={"server_id": str(user.user.uuid)},
95
+ )
96
+ logger.log(logging.INFO, f"🥳 New User Created: {user.user.uuid}")
97
+
98
+ logger.info(f'Stripe subscription {event["type"]} for {customer_email}')
99
+ return {"success": success}
100
+
101
+
102
+ @subscription_router.patch("")
103
+ @requires(["authenticated"])
104
+ async def update_subscription(request: Request, operation: str):
105
+ # Retrieve the customer's details
106
+ email = request.user.object.email
107
+ customers = stripe.Customer.list(email=email).auto_paging_iter()
108
+ customer = next(customers, None)
109
+ if customer is None:
110
+ return {"success": False, "message": "Customer not found"}
111
+
112
+ if operation == "cancel":
113
+ customer_id = customer.id
114
+ for subscription in stripe.Subscription.list(customer=customer_id):
115
+ stripe.Subscription.modify(subscription.id, cancel_at_period_end=True)
116
+ return {"success": True}
117
+
118
+ elif operation == "resubscribe":
119
+ subscriptions = stripe.Subscription.list(customer=customer.id).auto_paging_iter()
120
+ # Find the subscription that is set to cancel at the end of the period
121
+ for subscription in subscriptions:
122
+ if subscription.cancel_at_period_end:
123
+ # Update the subscription to not cancel at the end of the period
124
+ stripe.Subscription.modify(subscription.id, cancel_at_period_end=False)
125
+ return {"success": True}
126
+ return {"success": False, "message": "No subscription found that is set to cancel"}
127
+
128
+ return {"success": False, "message": "Invalid operation"}
129
+
130
+
131
+ @subscription_router.post("/trial", response_class=Response)
132
+ @requires(["authenticated"])
133
+ async def start_trial(request: Request) -> Response:
134
+ user: KhojUser = request.user.object
135
+
136
+ # Start a trial for the user
137
+ updated_subscription = await adapters.astart_trial_subscription(user)
138
+
139
+ # Return trial status as a JSON response
140
+ return Response(
141
+ content=json.dumps({"trial_enabled": updated_subscription is not None}),
142
+ media_type="application/json",
143
+ status_code=200,
144
+ )
khoj/routers/auth.py ADDED
@@ -0,0 +1,307 @@
1
+ import asyncio
2
+ import datetime
3
+ import logging
4
+ import os
5
+ from typing import Optional
6
+ from urllib.parse import parse_qs, urlencode, urlparse, urlunparse
7
+
8
+ import requests
9
+ from fastapi import APIRouter, Depends
10
+ from pydantic import BaseModel, EmailStr
11
+ from starlette.authentication import requires
12
+ from starlette.config import Config
13
+ from starlette.requests import Request
14
+ from starlette.responses import HTMLResponse, RedirectResponse, Response
15
+ from starlette.status import HTTP_302_FOUND
16
+
17
+ from khoj.app.settings import DISABLE_HTTPS
18
+ from khoj.database.adapters import (
19
+ acreate_khoj_token,
20
+ aget_or_create_user_by_email,
21
+ aget_user_validated_by_email_verification_code,
22
+ delete_khoj_token,
23
+ get_khoj_tokens,
24
+ get_or_create_user,
25
+ )
26
+ from khoj.routers.email import send_magic_link_email, send_welcome_email
27
+ from khoj.routers.helpers import (
28
+ EmailVerificationApiRateLimiter,
29
+ get_next_url,
30
+ update_telemetry_state,
31
+ )
32
+ from khoj.utils import state
33
+
34
+ logger = logging.getLogger(__name__)
35
+
36
+ auth_router = APIRouter()
37
+
38
+
39
+ class MagicLinkForm(BaseModel):
40
+ email: EmailStr
41
+
42
+
43
+ if not state.anonymous_mode:
44
+ missing_requirements = []
45
+ from authlib.integrations.starlette_client import OAuth, OAuthError
46
+
47
+ try:
48
+ from google.auth.transport import requests as google_requests
49
+ from google.oauth2 import id_token
50
+ except ImportError:
51
+ missing_requirements += ["Install the Khoj production package with `pip install khoj[prod]`"]
52
+ if not os.environ.get("RESEND_API_KEY") and (
53
+ not os.environ.get("GOOGLE_CLIENT_ID") or not os.environ.get("GOOGLE_CLIENT_SECRET")
54
+ ):
55
+ missing_requirements += [
56
+ "Set your RESEND_API_KEY or GOOGLE_CLIENT_ID and GOOGLE_CLIENT_SECRET as environment variables"
57
+ ]
58
+ if missing_requirements:
59
+ requirements_string = "\n - " + "\n - ".join(missing_requirements)
60
+ error_msg = f"🚨 Start Khoj with --anonymous-mode flag or to enable authentication:{requirements_string}"
61
+ logger.error(error_msg)
62
+
63
+ config = Config(environ=os.environ)
64
+
65
+ oauth = OAuth(config)
66
+
67
+ CONF_URL = "https://accounts.google.com/.well-known/openid-configuration"
68
+ oauth.register(name="google", server_metadata_url=CONF_URL, client_kwargs={"scope": "openid email profile"})
69
+
70
+
71
+ @auth_router.get("/login")
72
+ async def login_get(request: Request):
73
+ redirect_uri = str(request.app.url_path_for("auth"))
74
+ return await oauth.google.authorize_redirect(request, redirect_uri)
75
+
76
+
77
+ @auth_router.post("/login")
78
+ async def login(request: Request):
79
+ redirect_uri = str(request.app.url_path_for("auth"))
80
+ return await oauth.google.authorize_redirect(request, redirect_uri)
81
+
82
+
83
+ @auth_router.post("/magic")
84
+ async def login_magic_link(request: Request, form: MagicLinkForm):
85
+ if request.user.is_authenticated:
86
+ # Clear the session if user is already authenticated
87
+ request.session.pop("user", None)
88
+
89
+ user, is_new = await aget_or_create_user_by_email(form.email)
90
+
91
+ if user:
92
+ unique_id = user.email_verification_code
93
+ await send_magic_link_email(user.email, unique_id, request.base_url)
94
+ if is_new:
95
+ update_telemetry_state(
96
+ request=request,
97
+ telemetry_type="api",
98
+ api="create_user__email",
99
+ metadata={"server_id": str(user.uuid)},
100
+ )
101
+ logger.log(logging.INFO, f"🥳 New User Created: {user.uuid}")
102
+
103
+ return Response(status_code=200)
104
+
105
+
106
+ @auth_router.get("/magic")
107
+ async def sign_in_with_magic_link(
108
+ request: Request,
109
+ code: str,
110
+ email: str,
111
+ rate_limiter=Depends(
112
+ EmailVerificationApiRateLimiter(requests=10, window=60 * 60 * 24, slug="magic_link_verification")
113
+ ),
114
+ ):
115
+ user, code_is_expired = await aget_user_validated_by_email_verification_code(code, email)
116
+
117
+ if user:
118
+ if code_is_expired:
119
+ request.session["user"] = {}
120
+ return Response(status_code=403)
121
+
122
+ id_info = {
123
+ "email": user.email,
124
+ }
125
+
126
+ request.session["user"] = dict(id_info)
127
+ return RedirectResponse(url="/")
128
+ return Response(status_code=401)
129
+
130
+
131
+ @auth_router.post("/token")
132
+ @requires(["authenticated"], redirect="login_page")
133
+ async def generate_token(request: Request, token_name: Optional[str] = None):
134
+ "Generate API token for given user"
135
+ if token_name:
136
+ token = await acreate_khoj_token(user=request.user.object, name=token_name)
137
+ else:
138
+ token = await acreate_khoj_token(user=request.user.object)
139
+ return {
140
+ "token": token.token,
141
+ "name": token.name,
142
+ }
143
+
144
+
145
+ @auth_router.get("/token")
146
+ @requires(["authenticated"], redirect="login_page")
147
+ def get_tokens(request: Request):
148
+ "Get API tokens enabled for given user"
149
+ tokens = get_khoj_tokens(user=request.user.object)
150
+ return tokens
151
+
152
+
153
+ @auth_router.delete("/token")
154
+ @requires(["authenticated"], redirect="login_page")
155
+ async def delete_token(request: Request, token: str):
156
+ "Delete API token for given user"
157
+ return await delete_khoj_token(user=request.user.object, token=token)
158
+
159
+
160
+ @auth_router.post("/redirect")
161
+ async def auth_post(request: Request):
162
+ # This is maintained for compatibility with the /login endpoint
163
+ form = await request.form()
164
+ next_url = get_next_url(request)
165
+ for q in request.query_params:
166
+ if q != "next":
167
+ next_url += f"&{q}={request.query_params[q]}"
168
+
169
+ credential = form.get("credential")
170
+
171
+ csrf_token_cookie = request.cookies.get("g_csrf_token")
172
+ if not csrf_token_cookie:
173
+ logger.info("Missing CSRF token. Redirecting user to login page")
174
+ return RedirectResponse(url=next_url)
175
+ csrf_token_body = form.get("g_csrf_token")
176
+ if not csrf_token_body:
177
+ logger.info("Missing CSRF token body. Redirecting user to login page")
178
+ return RedirectResponse(url=next_url)
179
+ if csrf_token_cookie != csrf_token_body:
180
+ return Response("Invalid CSRF token", status_code=400)
181
+
182
+ try:
183
+ idinfo = id_token.verify_oauth2_token(credential, google_requests.Request(), os.environ["GOOGLE_CLIENT_ID"])
184
+ except OAuthError as error:
185
+ return HTMLResponse(f"<h1>{error.error}</h1>")
186
+ khoj_user = await get_or_create_user(idinfo)
187
+
188
+ if khoj_user:
189
+ request.session["user"] = dict(idinfo)
190
+
191
+ if datetime.timedelta(minutes=3) > (datetime.datetime.now(datetime.timezone.utc) - khoj_user.date_joined):
192
+ asyncio.create_task(send_welcome_email(idinfo["name"], idinfo["email"]))
193
+ update_telemetry_state(
194
+ request=request,
195
+ telemetry_type="api",
196
+ api="create_user__google",
197
+ metadata={"server_id": str(khoj_user.uuid)},
198
+ )
199
+ logger.log(logging.INFO, f"🥳 New User Created: {khoj_user.uuid}")
200
+ return RedirectResponse(url=next_url, status_code=HTTP_302_FOUND)
201
+
202
+ return RedirectResponse(url=next_url, status_code=HTTP_302_FOUND)
203
+
204
+
205
+ @auth_router.get("/redirect")
206
+ async def auth(request: Request):
207
+ next_url_path = get_next_url(request)
208
+
209
+ # Add query params from request, excluding OAuth params to next URL
210
+ oauth_params = {"code", "state", "scope", "authuser", "prompt", "session_state", "access_type", "next"}
211
+ query_params = {param: value for param, value in request.query_params.items() if param not in oauth_params}
212
+
213
+ # Rebuild next URL with updated query params
214
+ parsed_next_url_path = urlparse(next_url_path)
215
+ next_url = urlunparse(
216
+ (
217
+ parsed_next_url_path.scheme,
218
+ parsed_next_url_path.netloc,
219
+ parsed_next_url_path.path,
220
+ parsed_next_url_path.params,
221
+ urlencode(query_params, doseq=True),
222
+ parsed_next_url_path.fragment,
223
+ )
224
+ )
225
+
226
+ # Construct the full redirect URI including domain
227
+ base_url = str(request.base_url).rstrip("/")
228
+ if not DISABLE_HTTPS:
229
+ base_url = base_url.replace("http://", "https://")
230
+ redirect_uri = f"{base_url}{request.app.url_path_for('auth')}"
231
+
232
+ # Build the payload for the token request
233
+ code = request.query_params.get("code")
234
+ payload = {
235
+ "code": code,
236
+ "client_id": os.environ["GOOGLE_CLIENT_ID"],
237
+ "client_secret": os.environ["GOOGLE_CLIENT_SECRET"],
238
+ "redirect_uri": redirect_uri,
239
+ "grant_type": "authorization_code",
240
+ }
241
+
242
+ # Request the token from Google
243
+ verified_data = requests.post(
244
+ "https://oauth2.googleapis.com/token",
245
+ headers={"Content-Type": "application/x-www-form-urlencoded"},
246
+ data=payload,
247
+ )
248
+
249
+ # Validate the OAuth response
250
+ if verified_data.status_code != 200:
251
+ logger.error(f"Token request failed: {verified_data.text}")
252
+ try:
253
+ error_json = verified_data.json()
254
+ logger.error(f"Error response JSON for Google verification: {error_json}")
255
+ except ValueError:
256
+ logger.error("Response content is not valid JSON")
257
+ verified_data.raise_for_status()
258
+
259
+ credential = verified_data.json().get("id_token")
260
+ if not credential:
261
+ logger.error("Missing id_token in OAuth response")
262
+ return RedirectResponse(url="/login?error=invalid_token", status_code=HTTP_302_FOUND)
263
+
264
+ # Validate the OAuth token
265
+ try:
266
+ idinfo = id_token.verify_oauth2_token(credential, google_requests.Request(), os.environ["GOOGLE_CLIENT_ID"])
267
+ except OAuthError as error:
268
+ return HTMLResponse(f"<h1>{error.error}</h1>")
269
+
270
+ # Get or create the authenticated user in the database
271
+ khoj_user = await get_or_create_user(idinfo)
272
+
273
+ # Set the user session if the user is authenticated
274
+ if khoj_user:
275
+ request.session["user"] = dict(idinfo)
276
+
277
+ # Send a welcome email to new users
278
+ if datetime.timedelta(minutes=3) > (datetime.datetime.now(datetime.timezone.utc) - khoj_user.date_joined):
279
+ asyncio.create_task(send_welcome_email(idinfo["name"], idinfo["email"]))
280
+ update_telemetry_state(
281
+ request=request,
282
+ telemetry_type="api",
283
+ api="create_user__google",
284
+ metadata={"server_id": str(khoj_user.uuid)},
285
+ )
286
+ logger.log(logging.INFO, f"🥳 New User Created: {khoj_user.uuid}")
287
+
288
+ # Redirect the user to the next URL
289
+ return RedirectResponse(url=next_url, status_code=HTTP_302_FOUND)
290
+
291
+
292
+ @auth_router.get("/logout")
293
+ async def logout(request: Request):
294
+ request.session.pop("user", None)
295
+ return RedirectResponse(url="/")
296
+
297
+
298
+ @auth_router.get("/oauth/metadata")
299
+ async def oauth_metadata(request: Request):
300
+ redirect_uri = str(request.app.url_path_for("auth"))
301
+
302
+ return {
303
+ "google": {
304
+ "client_id": os.environ.get("GOOGLE_CLIENT_ID"),
305
+ "redirect_uri": f"{redirect_uri}",
306
+ }
307
+ }