Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add a unique_id field for identifiying conversations #914

Merged
merged 3 commits into from
Sep 16, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
38 changes: 36 additions & 2 deletions src/interface/web/app/chat/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,8 @@ interface ChatBodyDataProps {

function ChatBodyData(props: ChatBodyDataProps) {
const searchParams = useSearchParams();
const conversationId = searchParams.get("conversationId");
const conversationUniqueId = searchParams.get("v");
const [conversationId, setConversationId] = useState<string | null>("");
const [message, setMessage] = useState("");
const [image, setImage] = useState<string | null>(null);
const [processingMessage, setProcessingMessage] = useState(false);
Expand Down Expand Up @@ -60,6 +61,11 @@ function ChatBodyData(props: ChatBodyDataProps) {
setProcessingMessage(true);
setQueryToProcess(storedMessage);
}

const conversationId = localStorage.getItem("conversationId");
if (conversationId) {
setConversationId(conversationId);
}
}, [setQueryToProcess]);

useEffect(() => {
Expand All @@ -69,6 +75,30 @@ function ChatBodyData(props: ChatBodyDataProps) {
}
}, [message, setQueryToProcess]);

useEffect(() => {
if (!conversationUniqueId) {
return;
}

fetch(
`/api/chat/metadata?conversation_unique_id=${encodeURIComponent(conversationUniqueId)}`,
)
.then((response) => {
if (!response.ok) {
throw new Error(response.statusText);
}
return response.json();
})
.then((data) => {
setConversationId(data.conversationId);
})
.catch((err) => {
console.error(err);
setConversationId(null);
return;
});
});

useEffect(() => {
if (conversationId) {
onConversationIdChange?.(conversationId);
Expand All @@ -87,11 +117,15 @@ function ChatBodyData(props: ChatBodyDataProps) {
}
}, [props.streamedMessages]);

if (!conversationId) {
if (!conversationUniqueId || conversationId === null) {
window.location.href = "/";
return;
}

if (!conversationId) {
return <Loading />;
}

return (
<>
<div className={false ? styles.chatBody : styles.chatBodyFull}>
Expand Down
13 changes: 10 additions & 3 deletions src/interface/web/app/common/chatFunctions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -186,6 +186,11 @@ export function modifyFileFilterForConversation(
});
}

interface NewConversationMetadata {
conversationId: string;
conversationUniqueId: string;
}

export async function createNewConversation(slug: string) {
try {
const response = await fetch(`/api/chat/sessions?client=web&agent_slug=${slug}`, {
Expand All @@ -194,9 +199,11 @@ export async function createNewConversation(slug: string) {
if (!response.ok)
throw new Error(`Failed to fetch chat sessions with status: ${response.status}`);
const data = await response.json();
const conversationID = data.conversation_id;
if (!conversationID) throw new Error("Conversation ID not found in response");
return conversationID;
const uniqueId = data.unique_id;
const conversationId = data.conversation_id;
if (!uniqueId) throw new Error("Unique ID not found in response");
if (!conversationId) throw new Error("Conversation ID not found in response");
return { conversationId, conversationUniqueId: uniqueId } as NewConversationMetadata;
} catch (error) {
console.error("Error creating new conversation:", error);
throw error;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,9 @@ interface ChatHistory {
compressed: boolean;
created: string;
updated: string;
unique_id: string;
showSidePanel: (isEnabled: boolean) => void;
selectedConversationId: string | null;
}

import {
Expand Down Expand Up @@ -398,6 +400,7 @@ interface SessionsAndFilesProps {
conversationId: string | null;
uploadedFiles: string[];
isMobileWidth: boolean;
selectedConversationId: string | null;
}

function SessionsAndFiles(props: SessionsAndFilesProps) {
Expand Down Expand Up @@ -435,6 +438,10 @@ function SessionsAndFiles(props: SessionsAndFilesProps) {
agent_avatar={chatHistory.agent_avatar}
agent_name={chatHistory.agent_name}
showSidePanel={props.setEnabled}
unique_id={chatHistory.unique_id}
selectedConversationId={
props.selectedConversationId
}
/>
),
)}
Expand All @@ -446,6 +453,7 @@ function SessionsAndFiles(props: SessionsAndFilesProps) {
<ChatSessionsModal
data={props.organizedData}
showSidePanel={props.setEnabled}
selectedConversationId={props.selectedConversationId}
/>
)}
</div>
Expand Down Expand Up @@ -640,20 +648,18 @@ function ChatSessionActionMenu(props: ChatSessionActionMenuProps) {
function ChatSession(props: ChatHistory) {
const [isHovered, setIsHovered] = useState(false);
const [title, setTitle] = useState(props.slug || "New Conversation 🌱");
var currConversationId = parseInt(
new URLSearchParams(window.location.search).get("conversationId") || "-1",
);
var currConversationId =
props.conversation_id &&
props.selectedConversationId &&
parseInt(props.conversation_id) === parseInt(props.selectedConversationId);
return (
<div
onMouseEnter={() => setIsHovered(true)}
onMouseLeave={() => setIsHovered(false)}
key={props.conversation_id}
className={`${styles.session} ${props.compressed ? styles.compressed : "!max-w-full"} ${isHovered ? `${styles.sessionHover}` : ""} ${currConversationId === parseInt(props.conversation_id) && currConversationId != -1 ? "dark:bg-neutral-800 bg-white" : ""}`}
className={`${styles.session} ${props.compressed ? styles.compressed : "!max-w-full"} ${isHovered ? `${styles.sessionHover}` : ""} ${currConversationId ? "dark:bg-neutral-800 bg-white" : ""}`}
>
<Link
href={`/chat?conversationId=${props.conversation_id}`}
onClick={() => props.showSidePanel(false)}
>
<Link href={`/chat?v=${props.unique_id}`} onClick={() => props.showSidePanel(false)}>
<p className={styles.session}>{title}</p>
</Link>
<ChatSessionActionMenu conversationId={props.conversation_id} setTitle={setTitle} />
Expand All @@ -664,9 +670,14 @@ function ChatSession(props: ChatHistory) {
interface ChatSessionsModalProps {
data: GroupedChatHistory | null;
showSidePanel: (isEnabled: boolean) => void;
selectedConversationId: string | null;
}

function ChatSessionsModal({ data, showSidePanel }: ChatSessionsModalProps) {
function ChatSessionsModal({
data,
showSidePanel,
selectedConversationId,
}: ChatSessionsModalProps) {
return (
<Dialog>
<DialogTrigger className="flex text-left text-medium text-gray-500 hover:text-gray-300 cursor-pointer my-4 text-sm p-[0.5rem]">
Expand Down Expand Up @@ -698,6 +709,8 @@ function ChatSessionsModal({ data, showSidePanel }: ChatSessionsModalProps) {
agent_avatar={chatHistory.agent_avatar}
agent_name={chatHistory.agent_name}
showSidePanel={showSidePanel}
unique_id={chatHistory.unique_id}
selectedConversationId={selectedConversationId}
/>
))}
</div>
Expand Down Expand Up @@ -819,6 +832,7 @@ export default function SidePanel(props: SidePanelProps) {
userProfile={authenticatedData}
conversationId={props.conversationId}
isMobileWidth={props.isMobileWidth}
selectedConversationId={props.conversationId}
/>
</div>
) : (
Expand Down Expand Up @@ -887,6 +901,7 @@ export default function SidePanel(props: SidePanelProps) {
userProfile={authenticatedData}
conversationId={props.conversationId}
isMobileWidth={props.isMobileWidth}
selectedConversationId={props.conversationId}
/>
</div>
)}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -408,7 +408,7 @@ export const suggestionsData: Suggestion[] = [
link: "",
},
{
type: SuggestionType.Code,
type: SuggestionType.Interviewing,
color: suggestionToColorMap[SuggestionType.Interviewing] || DEFAULT_COLOR,
description: "Provide tips for writing an effective resume.",
link: "",
Expand Down
9 changes: 6 additions & 3 deletions src/interface/web/app/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -138,10 +138,13 @@ function ChatBodyData(props: ChatBodyDataProps) {
if (message && !processingMessage) {
setProcessingMessage(true);
try {
const newConversationId = await createNewConversation(selectedAgent || "khoj");
onConversationIdChange?.(newConversationId);
window.location.href = `/chat?conversationId=${newConversationId}`;
const newConversationMetadata = await createNewConversation(
selectedAgent || "khoj",
);
onConversationIdChange?.(newConversationMetadata.conversationId);
window.location.href = `/chat?v=${newConversationMetadata.conversationUniqueId}`;
localStorage.setItem("message", message);
localStorage.setItem("conversationId", newConversationMetadata.conversationId);
if (image) {
localStorage.setItem("image", image);
}
Expand Down
4 changes: 4 additions & 0 deletions src/khoj/database/adapters/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -679,6 +679,10 @@ def get_conversation_by_user(

return conversation

@staticmethod
def get_conversation_by_unique_id(user: KhojUser, unique_id: str):
return Conversation.objects.filter(unique_id=unique_id, user=user).first()

@staticmethod
def get_conversation_sessions(user: KhojUser, client_application: ClientApplication = None):
return (
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
# Generated by Django 5.0.8 on 2024-09-16 04:12

import uuid

from django.db import migrations, models


class Migration(migrations.Migration):
dependencies = [
("database", "0062_merge_20240913_0222"),
]

operations = [
migrations.AddField(
model_name="conversation",
name="unique_id",
field=models.UUIDField(default=None, editable=False, null=True),
),
]
20 changes: 20 additions & 0 deletions src/khoj/database/migrations/0064_populate_unique_id.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import uuid

from django.db import migrations


def populate_unique_id(apps, schema_editor):
Conversation = apps.get_model("database", "Conversation")
for conversation in Conversation.objects.all():
conversation.unique_id = uuid.uuid4()
conversation.save()


class Migration(migrations.Migration):
dependencies = [
("database", "0063_conversation_add_unique_id_field"),
]

operations = [
migrations.RunPython(populate_unique_id),
]
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import uuid

from django.db import migrations, models


class Migration(migrations.Migration):
dependencies = [
("database", "0064_populate_unique_id"),
]

operations = [
migrations.AlterField(
model_name="conversation",
name="unique_id",
field=models.UUIDField(default=uuid.uuid4, editable=False, unique=True),
),
]
1 change: 1 addition & 0 deletions src/khoj/database/models/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -350,6 +350,7 @@ class Conversation(BaseModel):
title = models.CharField(max_length=200, default=None, null=True, blank=True)
agent = models.ForeignKey(Agent, on_delete=models.SET_NULL, default=None, null=True, blank=True)
file_filters = models.JSONField(default=list)
unique_id = models.UUIDField(default=uuid.uuid4, editable=False, unique=True)


class PublicConversation(BaseModel):
Expand Down
33 changes: 31 additions & 2 deletions src/khoj/routers/api_chat.py
Original file line number Diff line number Diff line change
Expand Up @@ -224,6 +224,7 @@ def chat_history(
"conversation_id": conversation.id,
"slug": conversation.title if conversation.title else conversation.slug,
"agent": agent_metadata,
"unique_id": conversation.unique_id,
}
)

Expand All @@ -245,6 +246,33 @@ def chat_history(
return {"status": "ok", "response": meta_log}


@api_chat.get("/metadata")
def get_chat_metadata(
request: Request,
common: CommonQueryParams,
conversation_unique_id: str,
):
user = request.user.object

# Load Conversation Metadata
conversation = ConversationAdapters.get_conversation_by_unique_id(user, conversation_unique_id)

if conversation is None:
return Response(
content=json.dumps({"status": "error", "message": f"Conversation: {conversation_unique_id} not found"}),
status_code=404,
)

update_telemetry_state(
request=request,
telemetry_type="api",
api="chat_metadata",
**common.__dict__,
)

return {"status": "ok", "conversationId": conversation.id}


@api_chat.get("/share/history")
def get_shared_chat(
request: Request,
Expand Down Expand Up @@ -418,7 +446,7 @@ def chat_sessions(
conversations = conversations[:8]

sessions = conversations.values_list(
"id", "slug", "title", "agent__slug", "agent__name", "agent__avatar", "created_at", "updated_at"
"id", "slug", "title", "agent__slug", "agent__name", "agent__avatar", "created_at", "updated_at", "unique_id"
)

session_values = [
Expand All @@ -429,6 +457,7 @@ def chat_sessions(
"agent_avatar": session[5],
"created": session[6].strftime("%Y-%m-%d %H:%M:%S"),
"updated": session[7].strftime("%Y-%m-%d %H:%M:%S"),
"unique_id": str(session[8]),
}
for session in sessions
]
Expand All @@ -455,7 +484,7 @@ async def create_chat_session(
# Create new Conversation Session
conversation = await ConversationAdapters.acreate_conversation_session(user, request.user.client_app, agent_slug)

response = {"conversation_id": conversation.id}
response = {"conversation_id": conversation.id, "unique_id": str(conversation.unique_id)}

conversation_metadata = {
"agent": agent_slug,
Expand Down
Loading