Skip to content
Open
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
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ export const WidgetAuthScreen = () => {
}
});

const createContaxtSession = useMutation(api.public.contact_sessions.create)
const createContaxtSession = useMutation(api.public.contactSessions.create)

const onSubmit = async (values : z.infer<typeof formSchema>) => {
if(!organizationId) return;
Expand Down
111 changes: 97 additions & 14 deletions apps/widget/modules/widget/ui/screens/widget-chat-screen.tsx
Original file line number Diff line number Diff line change
@@ -1,11 +1,30 @@
"use client"

import { useThreadMessages , toUIMessages } from "@convex-dev/agent/react"
import { useAtom, useAtomValue, useSetAtom } from "jotai";
import { AlertTriangleIcon, ArrowLeftIcon, MenuIcon } from "lucide-react";
import { ArrowLeftIcon, MenuIcon } from "lucide-react";
import { contactSessionIdAtomFamily, conversationIDAtom, errorMessageAtom, organizationIdAtom, screenAtom } from "../../atoms/widget-atoms";
import { WidgetHeader } from "../components/widget.-header";
import { Button } from "@workspace/ui/components/button";
import { useQuery } from "convex/react";
import { useAction, useQuery } from "convex/react";
import { api } from "@workspace/backend/_generated/api";
import { set } from "react-hook-form";
import {
AIConversation , AIConversationContent , AIConversationScrollButton
} from "@workspace/ui/components/ui/conversation"
import {
AIInput,
AIInputButton,
AIInputSubmit,
AIInputTextarea,
AIInputToolbar,
AIInputTools
} from "@workspace/ui/components/ui/input"
import {
AIMessage,
AIMessageContent,
} from "@workspace/ui/components/ui/message"
import { AIResponse } from "@workspace/ui/components/ui/response"
import { useState } from "react";

export const WidgetChatScreen = () => {
const setScreen = useSetAtom(screenAtom);
Expand All @@ -15,21 +34,50 @@ export const WidgetChatScreen = () => {
const contactSessionId = useAtomValue(
contactSessionIdAtomFamily(organizationId || "")
)

const conversation = useQuery(
api.public.conversations.getOne,
conversationId && contactSessionId ? {
conversationId,
contactSessionId,
} : "skip"
)

const messages = useThreadMessages(
api.public.messages.getMany,
conversation?.threadId && contactSessionId
? {
threadId : conversation.threadId,
contactSessionId
} : "skip" ,
{ initialNumItems : 10 }
)
const onBack = () => {
setConversationId(null)
setScreen("selection")
}
return (
<>

const [input, setInput] = useState("");
const sendMessage = useAction(api.public.messages.create);

const handleSendMessage = async () => {
if (!input) return;

if(!conversationId || !contactSessionId) {
console.error("Conversation ID or Contact Session ID is missing.");
return;
}

await sendMessage({
threadId: conversation?.threadId!,
contactSessionId: contactSessionId,
prompt: input,
});


setInput(""); // Clear the input after sending
};

return (
<div className="flex flex-col h-full">
<WidgetHeader className="flex items-center justify-between">
<div className="flex items-center gap-x-2">
<Button onClick={() => onBack()} size="icon" variant="transparent">
Expand All @@ -41,11 +89,46 @@ export const WidgetChatScreen = () => {
<MenuIcon/>
</Button>
</WidgetHeader>
<div className="flex flex-1 flex-col gap-y-4 p-4">
<p className="text-sm">
{JSON.stringify(conversation)}
</p>
</div>
</>
<AIConversation className="flex-1 overflow-y-auto">
<AIConversationContent>
{toUIMessages(messages.results ?? [])?.map((message) => {
return (
<AIMessage
key={message.id}
from={message.role === "user" ? "user" : "assistant"}
>
<AIMessageContent>
<AIResponse>{message.content}</AIResponse>
</AIMessageContent>
{/* Add avatar component */}
</AIMessage>
)
})}
</AIConversationContent>
</AIConversation>

<AIInput className="px-3 py-4">
{/* <AIInputToolbar> */}
{/* Add tools here if needed */}
{/* </AIInputToolbar> */}
<div className="flex items-center space-x-2">
<AIInputTextarea
value={input}
onChange={(e) => setInput(e.target.value)}
placeholder="Type your message..."
onKeyDown={(e) => {
if (e.key !== 'Shift' && e.key !== 'Meta' && e.key !== 'Control' && e.key !== 'Alt') {
e.preventDefault();
handleSendMessage();
}
}}
/>
<AIInputSubmit status="ready" type="submit" onClick={handleSendMessage}>

</AIInputSubmit>
</div>
</AIInput>
</div>
)
}
}

Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@ export const WidgetLoadingScreen = ({ organizationId } : { organizationId : stri

// Validate sessions if it exists

const validateContactSessions = useMutation(api.public.contact_sessions.validate)
const validateContactSessions = useMutation(api.public.contactSessions.validate)

useEffect(() => {
if(step != "session") {
Expand Down
1 change: 1 addition & 0 deletions apps/widget/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
"typecheck": "tsc --noEmit"
},
"dependencies": {
"@convex-dev/agent": "~0.1.16",
"@hookform/resolvers": "^5.2.0",
"@vapi-ai/web": "^2.3.9",
"@workspace/backend": "workspace:*",
Expand Down
Loading