diff --git a/src/interface/web/app/settings/page.tsx b/src/interface/web/app/settings/page.tsx index 14929fa0..3be1b6bc 100644 --- a/src/interface/web/app/settings/page.tsx +++ b/src/interface/web/app/settings/page.tsx @@ -22,6 +22,10 @@ import { DropdownMenuRadioItem, DropdownMenuTrigger, } from "@/components/ui/dropdown-menu"; +import { + AlertDialog, AlertDialogAction, AlertDialogCancel, + AlertDialogContent, AlertDialogDescription, AlertDialogFooter, AlertDialogHeader, AlertDialogTitle, AlertDialogTrigger +} from "@/components/ui/alert-dialog"; import { Table, TableBody, TableCell, TableRow } from "@/components/ui/table"; import { @@ -54,6 +58,8 @@ import { Brain, EyeSlash, Eye, + Download, + TrashSimple, } from "@phosphor-icons/react"; import Loading from "../components/loading/loading"; @@ -63,6 +69,10 @@ import { SidebarInset, SidebarProvider, SidebarTrigger } from "@/components/ui/s import { AppSidebar } from "../components/appSidebar/appSidebar"; import { Separator } from "@/components/ui/separator"; import { KhojLogoType } from "../components/logo/khojLogo"; +import { Progress } from "@/components/ui/progress"; + +import JSZip from "jszip"; +import { saveAs } from 'file-saver'; interface DropdownComponentProps { items: ModelOptions[]; @@ -296,6 +306,10 @@ export default function SettingsView() { const [numberValidationState, setNumberValidationState] = useState( PhoneNumberValidationState.Verified, ); + const [isExporting, setIsExporting] = useState(false); + const [exportProgress, setExportProgress] = useState(0); + const [exportedConversations, setExportedConversations] = useState(0); + const [totalConversations, setTotalConversations] = useState(0); const { toast } = useToast(); const isMobileWidth = useIsMobileWidth(); @@ -311,8 +325,8 @@ export default function SettingsView() { initialUserConfig?.is_phone_number_verified ? PhoneNumberValidationState.Verified : initialUserConfig?.phone_number - ? PhoneNumberValidationState.SendOTP - : PhoneNumberValidationState.Setup, + ? PhoneNumberValidationState.SendOTP + : PhoneNumberValidationState.Setup, ); setName(initialUserConfig?.given_name); setNotionToken(initialUserConfig?.notion_token ?? null); @@ -538,6 +552,56 @@ export default function SettingsView() { } }; + const exportChats = async () => { + try { + setIsExporting(true); + + // Get total conversation count + const statsResponse = await fetch('/api/chat/stats'); + const stats = await statsResponse.json(); + const total = stats.num_conversations; + setTotalConversations(total); + + // Create zip file + const zip = new JSZip(); + const conversations = []; + + // Fetch all conversations in batches of 10 + for (let page = 0; page * 10 < total; page++) { + const response = await fetch(`/api/chat/export?page=${page}`); + const data = await response.json(); + conversations.push(...data); + + setExportedConversations((page + 1) * 10); + setExportProgress(((page + 1) * 10 / total) * 100); + } + + // Add conversations to zip + zip.file("conversations.json", JSON.stringify(conversations, null, 2)); + + // Generate and download zip + const content = await zip.generateAsync({ type: "blob" }); + saveAs(content, "khoj-conversations.zip"); + + toast({ + title: "Export Complete", + description: `Successfully exported ${conversations.length} conversations`, + }); + } catch (error) { + console.error("Error exporting chats:", error); + toast({ + title: "Export Failed", + description: "Failed to export chats. Please try again.", + variant: "destructive" + }); + } finally { + setIsExporting(false); + setExportProgress(0); + setExportedConversations(0); + setTotalConversations(0); + } + }; + const saveNotionToken = async () => { if (!notionToken) return; // Save Notion API key to server @@ -731,93 +795,93 @@ export default function SettingsView() { )) || (userConfig.subscription_state === "subscribed" && ( - <> -

- Futurist -

-

- Subscription renews on{" "} - - { - userConfig.subscription_renewal_date - } - -

- - )) || - (userConfig.subscription_state === - "unsubscribed" && ( - <> -

Futurist

-

- Subscription ends on{" "} - - { - userConfig.subscription_renewal_date - } - -

- - )) || - (userConfig.subscription_state === - "expired" && ( - <> -

Humanist

- {(userConfig.subscription_renewal_date && ( + <> +

+ Futurist +

- Subscription expired{" "} - on{" "} + Subscription renews on{" "} { userConfig.subscription_renewal_date }

- )) || ( + + )) || + (userConfig.subscription_state === + "unsubscribed" && ( + <> +

Futurist

- Check{" "} - - pricing page - {" "} - to compare plans. + Subscription ends on{" "} + + { + userConfig.subscription_renewal_date + } +

- )} - - ))} + + )) || + (userConfig.subscription_state === + "expired" && ( + <> +

Humanist

+ {(userConfig.subscription_renewal_date && ( +

+ Subscription expired{" "} + on{" "} + + { + userConfig.subscription_renewal_date + } + +

+ )) || ( +

+ Check{" "} + + pricing page + {" "} + to compare plans. +

+ )} + + ))} {(userConfig.subscription_state == "subscribed" && ( - - )) || - (userConfig.subscription_state == - "unsubscribed" && ( )) || + (userConfig.subscription_state == + "unsubscribed" && ( + + )) || (userConfig.subscription_enabled_trial_at && ( - ) : /* Show set API key button notion oauth url not set setup */ - !userConfig.notion_oauth_url ? ( - - ) : ( - <> - ) + userConfig.enabled_content_source.notion && + notionToken === + userConfig.notion_token ? ( + + ) : /* Show set API key button notion oauth url not set setup */ + !userConfig.notion_oauth_url ? ( + + ) : ( + <> + ) } - )) || ( - + )) || ( + - )} + !isValidPhoneNumber(phoneNumber) + } + onClick={sendOTP} + > + {!userConfig.phone_number ? ( + <> + + Setup Whatsapp + + ) : !phoneNumber || + (phoneNumber === + userConfig.phone_number && + numberValidationState === + PhoneNumberValidationState.Verified) || + !isValidPhoneNumber(phoneNumber) ? ( + <> + + Switch Number + + ) : ( + <> + Send OTP{" "} + + + )} + + )} {numberValidationState === PhoneNumberValidationState.Verified && ( - + + )} + + + + +
+
+ Account +
+
+ + + + Export Data + + +

+ Download all your chat conversations +

+ {exportProgress > 0 && ( +
+ +

+ Exported {exportedConversations} of {totalConversations} conversations +

+
)} +
+ + + +
+ + + + + Delete Account + + +

+ This will delete all your account data, including conversations, agents, and any assets you{"'"}ve generated. Be sure to export before you do this if you want to keep your information. +

+
+ + + + + + + + Are you absolutely sure? + + This action is irreversible. This will permanently delete your account + and remove all your data from our servers. + + + + Cancel + { + try { + const response = await fetch('/api/self', { + method: 'DELETE' + }); + if (!response.ok) throw new Error('Failed to delete account'); + + toast({ + title: "Account Deleted", + description: "Your account has been successfully deleted.", + }); + + // Redirect to home page after successful deletion + window.location.href = "/"; + } catch (error) { + console.error('Error deleting account:', error); + toast({ + title: "Error", + description: "Failed to delete account. Please try again or contact support.", + variant: "destructive" + }); + } + }} + > + + Delete Account + + + +
diff --git a/src/interface/web/package.json b/src/interface/web/package.json index 5cebb177..40f01955 100644 --- a/src/interface/web/package.json +++ b/src/interface/web/package.json @@ -43,6 +43,7 @@ "@radix-ui/react-toggle": "^1.1.0", "@radix-ui/react-tooltip": "^1.1.6", "@radix-ui/themes": "^3.1.1", + "@types/file-saver": "^2.0.7", "autoprefixer": "^10.4.19", "class-variance-authority": "^0.7.1", "clsx": "^2.1.1", @@ -53,9 +54,11 @@ "embla-carousel-react": "^8.5.1", "eslint": "^8", "eslint-config-next": "14.2.3", + "file-saver": "^2.0.5", "framer-motion": "^12.0.6", "input-otp": "^1.2.4", "intl-tel-input": "^23.8.1", + "jszip": "^3.10.1", "katex": "^0.16.21", "libphonenumber-js": "^1.11.4", "lucide-react": "^0.468.0", diff --git a/src/interface/web/yarn.lock b/src/interface/web/yarn.lock index ec65a1b2..cc3acabf 100644 --- a/src/interface/web/yarn.lock +++ b/src/interface/web/yarn.lock @@ -1185,6 +1185,11 @@ dependencies: dompurify "*" +"@types/file-saver@^2.0.7": + version "2.0.7" + resolved "https://registry.yarnpkg.com/@types/file-saver/-/file-saver-2.0.7.tgz#8dbb2f24bdc7486c54aa854eb414940bbd056f7d" + integrity sha512-dNKVfHd/jk0SkR/exKGj2ggkB45MAkzvWCaqLUUgkyjITkGNzH8H+yUwr+BLJUBjZOe9w8X3wgmXhZDRg1ED6A== + "@types/geojson@*": version "7946.0.16" resolved "https://registry.yarnpkg.com/@types/geojson/-/geojson-7946.0.16.tgz#8ebe53d69efada7044454e3305c19017d97ced2a" @@ -1770,6 +1775,11 @@ confbox@^0.2.1: resolved "https://registry.yarnpkg.com/confbox/-/confbox-0.2.1.tgz#ae39f2c99699afa451d00206479f15f9a1208a8b" integrity sha512-hkT3yDPFbs95mNCy1+7qNKC6Pro+/ibzYxtM2iqEigpf0sVw+bg4Zh9/snjsBcf990vfIsg5+1U7VyiyBb3etg== +core-util-is@~1.0.0: + version "1.0.3" + resolved "https://registry.yarnpkg.com/core-util-is/-/core-util-is-1.0.3.tgz#a6042d3634c2b27e9328f837b965fac83808db85" + integrity sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ== + cose-base@^1.0.0: version "1.0.3" resolved "https://registry.yarnpkg.com/cose-base/-/cose-base-1.0.3.tgz#650334b41b869578a543358b80cda7e0abe0a60a" @@ -2739,6 +2749,11 @@ file-entry-cache@^6.0.1: dependencies: flat-cache "^3.0.4" +file-saver@^2.0.5: + version "2.0.5" + resolved "https://registry.yarnpkg.com/file-saver/-/file-saver-2.0.5.tgz#d61cfe2ce059f414d899e9dd6d4107ee25670c38" + integrity sha512-P9bmyZ3h/PRG+Nzga+rbdI4OEpNDzAVyy74uVO9ATgzLK6VtAsYybF/+TOCvrc0MO793d6+42lLyZTw7/ArVzA== + fill-range@^7.1.1: version "7.1.1" resolved "https://registry.yarnpkg.com/fill-range/-/fill-range-7.1.1.tgz#44265d3cac07e3ea7dc247516380643754a05292" @@ -3065,6 +3080,11 @@ ignore@^5.2.0: resolved "https://registry.yarnpkg.com/ignore/-/ignore-5.3.2.tgz#3cd40e729f3643fd87cb04e50bf0eb722bc596f5" integrity sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g== +immediate@~3.0.5: + version "3.0.6" + resolved "https://registry.yarnpkg.com/immediate/-/immediate-3.0.6.tgz#9db1dbd0faf8de6fbe0f5dd5e56bb606280de69b" + integrity sha512-XXOFtyqDjNDAQxVfYxuF7g9Il/IbWmmlQg2MYKOH8ExIT1qg6xc4zyS3HaEEATgs1btfzxq15ciUiY7gjSXRGQ== + import-fresh@^3.2.1: version "3.3.1" resolved "https://registry.yarnpkg.com/import-fresh/-/import-fresh-3.3.1.tgz#9cecb56503c0ada1f2741dbbd6546e4b13b57ccf" @@ -3086,7 +3106,7 @@ inflight@^1.0.4: once "^1.3.0" wrappy "1" -inherits@2: +inherits@2, inherits@~2.0.3: version "2.0.4" resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c" integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ== @@ -3343,6 +3363,11 @@ isarray@^2.0.5: resolved "https://registry.yarnpkg.com/isarray/-/isarray-2.0.5.tgz#8af1e4c1221244cc62459faf38940d4e644a5723" integrity sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw== +isarray@~1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/isarray/-/isarray-1.0.0.tgz#bb935d48582cba168c06834957a54a3e07124f11" + integrity sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ== + isexe@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/isexe/-/isexe-2.0.0.tgz#e8fbf374dc556ff8947a10dcb0572d633f2cfa10" @@ -3427,6 +3452,16 @@ json5@^1.0.2: object.assign "^4.1.4" object.values "^1.1.6" +jszip@^3.10.1: + version "3.10.1" + resolved "https://registry.yarnpkg.com/jszip/-/jszip-3.10.1.tgz#34aee70eb18ea1faec2f589208a157d1feb091c2" + integrity sha512-xXDvecyTpGLrqFrvkrUSoxxfJI5AH7U8zxxtVclpsUtMCq4JQ290LY8AW5c7Ggnr/Y/oK+bQMbqK2qmtk3pN4g== + dependencies: + lie "~3.3.0" + pako "~1.0.2" + readable-stream "~2.3.6" + setimmediate "^1.0.5" + katex@^0.16.21, katex@^0.16.9: version "0.16.21" resolved "https://registry.yarnpkg.com/katex/-/katex-0.16.21.tgz#8f63c659e931b210139691f2cc7bb35166b792a3" @@ -3497,6 +3532,13 @@ libphonenumber-js@^1.11.4: resolved "https://registry.yarnpkg.com/libphonenumber-js/-/libphonenumber-js-1.12.5.tgz#8e6043a67112d4beedb8627b359a613f04d88fba" integrity sha512-DOjiaVjjSmap12ztyb4QgoFmUe/GbgnEXHu+R7iowk0lzDIjScvPAm8cK9RYTEobbRb0OPlwlZUGTTJPJg13Kw== +lie@~3.3.0: + version "3.3.0" + resolved "https://registry.yarnpkg.com/lie/-/lie-3.3.0.tgz#dcf82dee545f46074daf200c7c1c5a08e0f40f6a" + integrity sha512-UaiMJzeWRlEujzAuw5LokY1L5ecNQYZKfmyZ9L7wDHb/p5etKaxXhohBcrw0EYby+G/NA52vRSN4N39dxHAIwQ== + dependencies: + immediate "~3.0.5" + lilconfig@^3.0.0, lilconfig@^3.1.3: version "3.1.3" resolved "https://registry.yarnpkg.com/lilconfig/-/lilconfig-3.1.3.tgz#a1bcfd6257f9585bf5ae14ceeebb7b559025e4c4" @@ -3960,6 +4002,11 @@ package-manager-detector@^0.2.8: dependencies: quansync "^0.2.7" +pako@~1.0.2: + version "1.0.11" + resolved "https://registry.yarnpkg.com/pako/-/pako-1.0.11.tgz#6c9599d340d54dfd3946380252a35705a6b992bf" + integrity sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw== + parent-module@^1.0.0: version "1.0.1" resolved "https://registry.yarnpkg.com/parent-module/-/parent-module-1.0.1.tgz#691d2709e78c79fae3a156622452d00762caaaa2" @@ -4160,6 +4207,11 @@ prettier@3.3.3: resolved "https://registry.yarnpkg.com/prettier/-/prettier-3.3.3.tgz#30c54fe0be0d8d12e6ae61dbb10109ea00d53105" integrity sha512-i2tDNA0O5IrMO757lfrdQZCc2jPNDVntV0m/+4whiDfWaTKfMNgR7Qz0NAeGz/nRqF4m5/6CLzbP4/liHt12Ew== +process-nextick-args@~2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/process-nextick-args/-/process-nextick-args-2.0.1.tgz#7820d9b16120cc55ca9ae7792680ae7dba6d7fe2" + integrity sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag== + prop-types@^15.8.1: version "15.8.1" resolved "https://registry.yarnpkg.com/prop-types/-/prop-types-15.8.1.tgz#67d87bf1a694f48435cf332c24af10214a3140b5" @@ -4309,6 +4361,19 @@ read-cache@^1.0.0: dependencies: pify "^2.3.0" +readable-stream@~2.3.6: + version "2.3.8" + resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-2.3.8.tgz#91125e8042bba1b9887f49345f6277027ce8be9b" + integrity sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA== + dependencies: + core-util-is "~1.0.0" + inherits "~2.0.3" + isarray "~1.0.0" + process-nextick-args "~2.0.0" + safe-buffer "~5.1.1" + string_decoder "~1.1.1" + util-deprecate "~1.0.1" + readdirp@~3.6.0: version "3.6.0" resolved "https://registry.yarnpkg.com/readdirp/-/readdirp-3.6.0.tgz#74a370bd857116e245b29cc97340cd431a02a6c7" @@ -4433,6 +4498,11 @@ safe-array-concat@^1.1.3: has-symbols "^1.1.0" isarray "^2.0.5" +safe-buffer@~5.1.0, safe-buffer@~5.1.1: + version "5.1.2" + resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.1.2.tgz#991ec69d296e0313747d59bdfd2b745c35f8828d" + integrity sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g== + safe-push-apply@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/safe-push-apply/-/safe-push-apply-1.0.0.tgz#01850e981c1602d398c85081f360e4e6d03d27f5" @@ -4503,6 +4573,11 @@ set-proto@^1.0.0: es-errors "^1.3.0" es-object-atoms "^1.0.0" +setimmediate@^1.0.5: + version "1.0.5" + resolved "https://registry.yarnpkg.com/setimmediate/-/setimmediate-1.0.5.tgz#290cbb232e306942d7d7ea9b83732ab7856f8285" + integrity sha512-MATJdZp8sLqDl/68LfQmbP8zKPLQNV6BIZoIgrscFDQ+RsvK/BxeDQOgyxKKoh0y/8h3BqVFnCqQ/gd+reiIXA== + shadcn-ui@^0.9.0: version "0.9.5" resolved "https://registry.yarnpkg.com/shadcn-ui/-/shadcn-ui-0.9.5.tgz#b7f35b78f2c7fe0b71651fe542ed62f01795b4a6" @@ -4616,6 +4691,7 @@ string-argv@^0.3.2: integrity sha512-aqD2Q0144Z+/RqG52NeHEkZauTAUWJO8c6yTftGJKO3Tja5tUgIfmIl6kExvhtxSDP7fXB6DvzkfMpCd/F3G+Q== "string-width-cjs@npm:string-width@^4.2.0", string-width@^4.1.0: + name string-width-cjs version "4.2.3" resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010" integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g== @@ -4710,6 +4786,13 @@ string.prototype.trimstart@^1.0.8: define-properties "^1.2.1" es-object-atoms "^1.0.0" +string_decoder@~1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-1.1.1.tgz#9cf1611ba62685d7030ae9e4ba34149c3af03fc8" + integrity sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg== + dependencies: + safe-buffer "~5.1.0" + "strip-ansi-cjs@npm:strip-ansi@^6.0.1", strip-ansi@^6.0.0, strip-ansi@^6.0.1: version "6.0.1" resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9" @@ -5043,7 +5126,7 @@ use-sync-external-store@^1.2.2, use-sync-external-store@^1.4.0: resolved "https://registry.yarnpkg.com/use-sync-external-store/-/use-sync-external-store-1.4.0.tgz#adbc795d8eeb47029963016cefdf89dc799fcebc" integrity sha512-9WXSPC5fMv61vaupRkCKCxsPxBocVnwakBEkMIHHpkTTg6icbJtg6jzgtLDm4bl3cSHAca52rYWih0k4K3PfHw== -util-deprecate@^1.0.2: +util-deprecate@^1.0.2, util-deprecate@~1.0.1: version "1.0.2" resolved "https://registry.yarnpkg.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf" integrity sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw== diff --git a/src/khoj/database/adapters/__init__.py b/src/khoj/database/adapters/__init__.py index 058017d2..8fe5787f 100644 --- a/src/khoj/database/adapters/__init__.py +++ b/src/khoj/database/adapters/__init__.py @@ -939,6 +939,28 @@ class ConversationAdapters: return conversation + @staticmethod + @require_valid_user + def get_all_conversations_for_export(user: KhojUser, page: Optional[int] = 0): + all_conversations = Conversation.objects.filter(user=user).prefetch_related("agent")[page : page + 10] + histories = [] + for conversation in all_conversations: + history = { + "title": conversation.title, + "agent": conversation.agent.name if conversation.agent else "Khoj", + "created_at": datetime.strftime(conversation.created_at, "%Y-%m-%d %H:%M:%S"), + "updated_at": datetime.strftime(conversation.updated_at, "%Y-%m-%d %H:%M:%S"), + "conversation_log": conversation.conversation_log, + "file_filters": conversation.file_filters, + } + histories.append(history) + return histories + + @staticmethod + @require_valid_user + def get_num_conversations(user: KhojUser): + return Conversation.objects.filter(user=user).count() + @staticmethod @require_valid_user def get_conversation_sessions(user: KhojUser, client_application: ClientApplication = None): diff --git a/src/khoj/processor/conversation/prompts.py b/src/khoj/processor/conversation/prompts.py index 0c2b3bbe..e6975a0a 100644 --- a/src/khoj/processor/conversation/prompts.py +++ b/src/khoj/processor/conversation/prompts.py @@ -1361,6 +1361,7 @@ help_message = PromptTemplate.from_template( - **/default**: Chat using your knowledge base and Khoj's general knowledge for context. - **/online**: Chat using the internet as a source of information. - **/image**: Generate an image based on your message. +- **/research**: Go deeper in a topic for more accurate, in-depth responses. - **/help**: Show this help message. You are using the **{model}** model on the **{device}**. diff --git a/src/khoj/routers/api.py b/src/khoj/routers/api.py index 44375bca..ca8287a1 100644 --- a/src/khoj/routers/api.py +++ b/src/khoj/routers/api.py @@ -68,6 +68,14 @@ conversation_command_rate_limiter = ConversationCommandRateLimiter( ) +@api.delete("/self") +@requires(["authenticated"]) +def delete_self(request: Request): + user = request.user.object + user.delete() + return {"status": "ok"} + + @api.get("/search", response_model=List[SearchResponse]) @requires(["authenticated"]) async def search( diff --git a/src/khoj/routers/api_chat.py b/src/khoj/routers/api_chat.py index 83053bab..0b4821f4 100644 --- a/src/khoj/routers/api_chat.py +++ b/src/khoj/routers/api_chat.py @@ -94,6 +94,22 @@ conversation_command_rate_limiter = ConversationCommandRateLimiter( api_chat = APIRouter() +@api_chat.get("/stats", response_class=Response) +@requires(["authenticated"]) +def chat_stats(request: Request, common: CommonQueryParams) -> Response: + num_conversations = ConversationAdapters.get_num_conversations(request.user.object) + return Response( + content=json.dumps({"num_conversations": num_conversations}), media_type="application/json", status_code=200 + ) + + +@api_chat.get("/export", response_class=Response) +@requires(["authenticated"]) +def export_conversation(request: Request, common: CommonQueryParams, page: Optional[int] = 1) -> Response: + all_conversations = ConversationAdapters.get_all_conversations_for_export(request.user.object, page=page) + return Response(content=json.dumps(all_conversations), media_type="application/json", status_code=200) + + @api_chat.get("/conversation/file-filters/{conversation_id}", response_class=Response) @requires(["authenticated"]) def get_file_filter(request: Request, conversation_id: str) -> Response: