import { type LoaderFunctionArgs, type ActionFunctionArgs, } from "@remix-run/server-runtime"; import { sort } from "fast-sort"; import { useParams, useRevalidator, useNavigate } from "@remix-run/react"; import { parse } from "@conform-to/zod"; import { requireUserId, requireUser, requireWorkpace, } from "~/services/session.server"; import { getConversationAndHistory, getCurrentConversationRun, stopConversation, createConversation, CreateConversationSchema, } from "~/services/conversation.server"; import { type ConversationHistory } from "@core/database"; import { ConversationItem, ConversationTextarea, StreamingConversation, } from "~/components/conversation"; import { useTypedLoaderData } from "remix-typedjson"; import React from "react"; import { ScrollAreaWithAutoScroll } from "~/components/use-auto-scroll"; import { PageHeader } from "~/components/common/page-header"; import { Plus } from "lucide-react"; import { json } from "@remix-run/node"; import { env } from "~/env.server"; // Example loader accessing params export async function loader({ params, request }: LoaderFunctionArgs) { const user = await requireUser(request); const workspace = await requireWorkpace(request); const conversation = await getConversationAndHistory( params.conversationId as string, user.id, ); if (!conversation) { throw new Error("No conversation found"); } const run = await getCurrentConversationRun(conversation.id, workspace.id); return { conversation, run, apiURL: env.TRIGGER_API_URL }; } // Example action accessing params export async function action({ params, request }: ActionFunctionArgs) { if (request.method.toUpperCase() !== "POST") { return new Response("Method Not Allowed", { status: 405 }); } const userId = await requireUserId(request); const workspace = await requireWorkpace(request); const formData = await request.formData(); const { conversationId } = params; if (!conversationId) { throw new Error("No conversation"); } // Check if this is a stop request (isLoading = true means stop button was clicked) const message = formData.get("message"); // If no message, it's a stop request if (!message) { const result = await stopConversation(conversationId, workspace.id); return json(result); } // Otherwise, create a new conversation message const submission = parse(formData, { schema: CreateConversationSchema }); if (!submission.value || submission.intent !== "submit") { return json(submission); } const conversation = await createConversation(workspace?.id, userId, { message: submission.value.message, title: submission.value.title, conversationId: submission.value.conversationId, }); return json({ conversation }); } // Accessing params in the component export default function SingleConversation() { const { conversation, run, apiURL } = useTypedLoaderData(); const conversationHistory = conversation.ConversationHistory; const [conversationResponse, setConversationResponse] = React.useState< { conversationHistoryId: string; id: string; token: string } | undefined >(run); const { conversationId } = useParams(); const revalidator = useRevalidator(); const navigate = useNavigate(); React.useEffect(() => { if (run) { setConversationResponse(run); } }, [run]); const conversations = React.useMemo(() => { const lastConversationHistoryId = conversationResponse?.conversationHistoryId; // First sort the conversation history by creation time const sortedConversationHistory = sort(conversationHistory).asc( (ch) => ch.createdAt, ); const lastIndex = sortedConversationHistory.findIndex( (item) => item.id === lastConversationHistoryId, ); // Filter out any conversation history items that come after the lastConversationHistoryId return lastConversationHistoryId ? sortedConversationHistory.filter((_ch, currentIndex: number) => { return currentIndex <= lastIndex; }) : sortedConversationHistory; }, [conversationResponse, conversationHistory]); const getConversations = () => { return ( <> {conversations.map((ch: ConversationHistory) => { return ; })} ); }; if (typeof window === "undefined") { return null; } return ( <> , onClick: () => navigate("/home/conversation"), variant: "secondary", }, ]} />
{getConversations()} {conversationResponse && ( { setConversationResponse(undefined); revalidator.revalidate(); }} apiURL={apiURL} /> )}
{conversation?.status !== "need_approval" && ( { if (conversation) { setConversationResponse({ conversationHistoryId: conversation.conversationHistoryId, id: conversation.id, token: conversation.token, }); } }} /> )}
); }