Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
12770b5
add function to get default search model config async
sabaimran Apr 22, 2025
be42ac1
add a basic user memory object for storage
sabaimran Apr 22, 2025
dec90b4
add associated migrations for new memory obj
sabaimran Apr 22, 2025
0b18511
add user memory to the admin page and adapters
sabaimran Apr 22, 2025
6000522
connect associated logic for getting and updating memories
sabaimran Apr 22, 2025
f85b74b
add API endpoints for managing memories
sabaimran Apr 22, 2025
ff79f26
add support in the settings page for browsing, viewing memories
sabaimran Apr 22, 2025
af0a32c
fix type of relevant_memories and remove return in ai_update_memories
sabaimran Apr 22, 2025
e952165
wire up relevant_memories to helper methods where relevant
sabaimran Apr 22, 2025
8acbd11
fix typing of relevant_memories in generate diagram
sabaimran Apr 22, 2025
0b336bb
pass memory context to extract references and final response agent
sabaimran Apr 22, 2025
90d1930
fix mismatched type of memory_context
sabaimran Apr 22, 2025
35f7e5c
remove unnecessary preprocessing or memories todict
sabaimran Apr 22, 2025
908e6ed
run final completion func steps asynchronously to avoid blocking the …
sabaimran Apr 22, 2025
29e191f
Merge master and resolve conflicts since mem
debanjum Aug 27, 2025
7ada291
Reduce research tools shown user memories to doc search and researcher
debanjum Aug 28, 2025
08b42c5
Scope memories separately for each agent
debanjum Aug 28, 2025
fd58d95
Improve memory manager prompt, enforce json schema for output
debanjum Aug 29, 2025
4c497e9
Fix passing retrieved memories to memory manager for updation context
debanjum Aug 29, 2025
ef737fd
Increase memories limit. Make it configurable. Improve memory dedupe
debanjum Aug 29, 2025
f79dfbb
Do not delete data store in memory feature as unrelated
debanjum Aug 29, 2025
89f467a
Create management job to delete, generate memories from old chats
debanjum Aug 30, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
90 changes: 90 additions & 0 deletions src/interface/web/app/components/userMemory/userMemory.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
import { useState } from "react";
import { Input } from "@/components/ui/input";
import { Button } from "@/components/ui/button";
import { Pencil, TrashSimple, FloppyDisk, X } from "@phosphor-icons/react";
import { useToast } from "@/components/ui/use-toast";

export interface UserMemorySchema {
id: number;
raw: string;
created_at: string;
}

interface UserMemoryProps {
memory: UserMemorySchema;
onDelete: (id: number) => void;
onUpdate: (id: number, raw: string) => void;
}

export function UserMemory({ memory, onDelete, onUpdate }: UserMemoryProps) {
const [isEditing, setIsEditing] = useState(false);
const [content, setContent] = useState(memory.raw);
const { toast } = useToast();

const handleUpdate = () => {
onUpdate(memory.id, content);
setIsEditing(false);
toast({
title: "Memory Updated",
description: "Your memory has been successfully updated.",
});
};

const handleDelete = () => {
onDelete(memory.id);
toast({
title: "Memory Deleted",
description: "Your memory has been successfully deleted.",
});
};

return (
<div className="flex items-center gap-2 w-full">
{isEditing ? (
<>
<Input
value={content}
onChange={(e) => setContent(e.target.value)}
className="flex-1"
/>
<Button
variant="ghost"
size="icon"
onClick={handleUpdate}
title="Save"
>
<FloppyDisk className="h-4 w-4" />
</Button>
<Button
variant="ghost"
size="icon"
onClick={() => setIsEditing(false)}
title="Cancel"
>
<X className="h-4 w-4" />
</Button>
</>
) : (
<>
<Input value={memory.raw} readOnly className="flex-1" />
<Button
variant="ghost"
size="icon"
onClick={() => setIsEditing(true)}
title="Edit"
>
<Pencil className="h-4 w-4" />
</Button>
<Button
variant="ghost"
size="icon"
onClick={handleDelete}
title="Delete"
>
<TrashSimple className="h-4 w-4" />
</Button>
</>
)}
</div>
);
}
111 changes: 110 additions & 1 deletion src/interface/web/app/settings/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import { Button } from "@/components/ui/button";
import { InputOTP, InputOTPGroup, InputOTPSlot } from "@/components/ui/input-otp";
import { Input } from "@/components/ui/input";
import { Card, CardContent, CardFooter, CardHeader } from "@/components/ui/card";

import {
DropdownMenu,
DropdownMenuContent,
Expand All @@ -33,6 +34,15 @@ import {
AlertDialogTitle,
AlertDialogTrigger,
} from "@/components/ui/alert-dialog";

import {
Dialog,
DialogContent,
DialogHeader,
DialogTitle,
DialogTrigger
} from "@/components/ui/dialog";

import { Table, TableBody, TableCell, TableRow } from "@/components/ui/table";

import {
Expand Down Expand Up @@ -74,6 +84,7 @@ import Loading from "../components/loading/loading";
import IntlTelInput from "intl-tel-input/react";
import { SidebarInset, SidebarProvider, SidebarTrigger } from "@/components/ui/sidebar";
import { AppSidebar } from "../components/appSidebar/appSidebar";
import { UserMemory, UserMemorySchema } from "../components/userMemory/userMemory";
import { Separator } from "@/components/ui/separator";
import { KhojLogoType } from "../components/logo/khojLogo";
import { Progress } from "@/components/ui/progress";
Expand Down Expand Up @@ -323,6 +334,7 @@ export default function SettingsView() {
const [numberValidationState, setNumberValidationState] = useState<PhoneNumberValidationState>(
PhoneNumberValidationState.Verified,
);
const [memories, setMemories] = useState<UserMemorySchema[]>([]);
const [isExporting, setIsExporting] = useState(false);
const [exportProgress, setExportProgress] = useState(0);
const [exportedConversations, setExportedConversations] = useState(0);
Expand Down Expand Up @@ -666,6 +678,65 @@ export default function SettingsView() {
}
};

const fetchMemories = async () => {
try {
console.log("Fetching memories...");
const response = await fetch('/api/memories/');
if (!response.ok) throw new Error('Failed to fetch memories');
const data = await response.json();
setMemories(data);
} catch (error) {
console.error('Error fetching memories:', error);
toast({
title: "Error",
description: "Failed to fetch memories. Please try again.",
variant: "destructive"
});
}
};

const handleDeleteMemory = async (id: number) => {
try {
const response = await fetch(`/api/memories/${id}`, {
method: 'DELETE'
});
if (!response.ok) throw new Error('Failed to delete memory');
setMemories(memories.filter(memory => memory.id !== id));
} catch (error) {
console.error('Error deleting memory:', error);
toast({
title: "Error",
description: "Failed to delete memory. Please try again.",
variant: "destructive"
});
}
};

const handleUpdateMemory = async (id: number, raw: string) => {
try {
const response = await fetch(`/api/memories/${id}`, {
method: 'PUT',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({ raw, memory_id: id }),
});
if (!response.ok) throw new Error('Failed to update memory');
const updatedMemory: UserMemorySchema = await response.json();
setMemories(memories.map(memory =>
memory.id === id ? updatedMemory : memory
));
} catch (error) {
console.error('Error updating memory:', error);
toast({
title: "Error",
description: "Failed to update memory. Please try again.",
variant: "destructive"
});
}
};


const syncContent = async (type: string) => {
try {
const response = await fetch(`/api/content?t=${type}`, {
Expand Down Expand Up @@ -1269,7 +1340,45 @@ export default function SettingsView() {
</Button>
</CardFooter>
</Card>

<Card className={cardClassName}>
<CardHeader className="text-xl flex flex-row">
<Brain className="h-7 w-7 mr-2" />
Memories
</CardHeader>
<CardContent className="overflow-hidden">
<p className="pb-4 text-gray-400">
View and manage your long-term memories
</p>
</CardContent>
<CardFooter className="flex flex-wrap gap-4">
<Dialog onOpenChange={(open) => open && fetchMemories()}>
<DialogTrigger asChild>
<Button variant="outline">
<Brain className="h-5 w-5 mr-2" />
Browse Memories
</Button>
</DialogTrigger>
<DialogContent className="max-w-2xl max-h-[80vh] overflow-y-auto">
<DialogHeader>
<DialogTitle>Your Memories</DialogTitle>
</DialogHeader>
<div className="grid gap-4 py-4">
{memories.map((memory) => (
<UserMemory
key={memory.id}
memory={memory}
onDelete={handleDeleteMemory}
onUpdate={handleUpdateMemory}
/>
))}
{memories.length === 0 && (
<p className="text-center text-gray-500">No memories found</p>
)}
</div>
</DialogContent>
</Dialog>
</CardFooter>
</Card>
<Card className={cardClassName}>
<CardHeader className="text-xl flex flex-row">
<TrashSimple className="h-7 w-7 mr-2 text-red-500" />
Expand Down
2 changes: 2 additions & 0 deletions src/khoj/configure.py
Original file line number Diff line number Diff line change
Expand Up @@ -323,6 +323,7 @@ def configure_routes(app):
from khoj.routers.api_automation import api_automation
from khoj.routers.api_chat import api_chat
from khoj.routers.api_content import api_content
from khoj.routers.api_memories import api_memories
from khoj.routers.api_model import api_model
from khoj.routers.notion import notion_router
from khoj.routers.web_client import web_client
Expand All @@ -332,6 +333,7 @@ def configure_routes(app):
app.include_router(api_agents, prefix="/api/agents")
app.include_router(api_automation, prefix="/api/automation")
app.include_router(api_model, prefix="/api/model")
app.include_router(api_memories, prefix="/api/memories")
app.include_router(api_content, prefix="/api/content")
app.include_router(notion_router, prefix="/api/notion")
app.include_router(web_client)
Expand Down
Loading
Loading