import type { Message } from 'ai'; import React, { useState } from 'react'; import { classNames } from '~/utils/classNames'; import { AssistantMessage, getAnnotationsTokensUsage } from './AssistantMessage'; import { UserMessage } from './UserMessage'; import { useLocation } from '@remix-run/react'; import { db, chatId } from '~/lib/persistence/useChatHistory'; import { forkChat } from '~/lib/persistence/db'; import { toast } from 'react-toastify'; import WithTooltip from '~/components/ui/Tooltip'; import { assert, sendCommandDedicatedClient } from "~/lib/replay/ReplayProtocolClient"; import ReactModal from 'react-modal'; ReactModal.setAppElement('#root'); interface MessagesProps { id?: string; className?: string; isStreaming?: boolean; messages?: Message[]; } // Combines information about the contents of a project along with a prompt // from the user and any associated Replay data to accomplish a task. Together // this information is enough that the model should be able to generate a // suitable fix. // // Must be JSON serializable. interface ProjectPrompt { content: string; // base64 encoded uniqueProjectName: string; input: string; } export interface BoltProblem { title: string; description: string; name: string; email: string; prompt: ProjectPrompt; } const gProjectPromptsByMessageId = new Map(); export function saveProjectPrompt(messageId: string, prompt: ProjectPrompt) { gProjectPromptsByMessageId.set(messageId, prompt); } export const Messages = React.forwardRef((props: MessagesProps, ref) => { const { id, isStreaming = false, messages = [] } = props; const location = useLocation(); const [isModalOpen, setIsModalOpen] = useState(false); const [currentProjectPrompt, setCurrentProjectPrompt] = useState(null); const [formData, setFormData] = useState({ title: '', description: '', name: '', email: '' }); const [problemId, setProblemId] = useState(null); const handleRewind = (messageId: string) => { const searchParams = new URLSearchParams(location.search); searchParams.set('rewindTo', messageId); window.location.search = searchParams.toString(); }; const handleFork = async (messageId: string) => { try { if (!db || !chatId.get()) { toast.error('Chat persistence is not available'); return; } const urlId = await forkChat(db, chatId.get()!, messageId); window.location.href = `/chat/${urlId}`; } catch (error) { toast.error('Failed to fork chat: ' + (error as Error).message); } }; const handleSaveProblem = (prompt: ProjectPrompt) => { setCurrentProjectPrompt(prompt); setIsModalOpen(true); setFormData({ title: '', description: '', name: '', email: '', }); setProblemId(null); }; const handleSubmitProblem = async (e: React.MouseEvent) => { // Add validation here if (!formData.title) { toast.error('Please fill in title field'); return; } toast.info("Submitting problem..."); console.log("SubmitProblem", formData); assert(currentProjectPrompt); const problem: BoltProblem = { title: formData.title, description: formData.description, name: formData.name, email: formData.email, prompt: currentProjectPrompt, }; try { const rv = await sendCommandDedicatedClient({ method: "Recording.globalExperimentalCommand", params: { name: "submitBoltProblem", params: { problem }, }, }); console.log("SubmitProblemRval", rv); setProblemId((rv as any).rval.problemId); } catch (error) { console.error("Error submitting problem", error); toast.error("Failed to submit problem"); } } const getLastMessageProjectPrompt = (index: number) => { // The message index is for the model response, and the project // prompt will be associated with the last message present when // the user prompt was sent to the model. So look back two messages // for the associated prompt. if (index < 2) { return null; } const previousMessage = messages[index - 2]; return gProjectPromptsByMessageId.get(previousMessage.id); }; const handleInputChange = (e: React.ChangeEvent) => { const { name, value } = e.target; setFormData(prev => ({ ...prev, [name]: value })); }; return ( <>
{messages.length > 0 ? messages.map((message, index) => { const { role, content, id: messageId } = message; const isUserMessage = role === 'user'; const isFirst = index === 0; const isLast = index === messages.length - 1; return (
{isUserMessage && (
)}
{isUserMessage ? ( ) : ( )}
{!isUserMessage && (
{messageId && (
)}
); }) : null} {isStreaming && (
)}
setIsModalOpen(false)} className="absolute top-1/2 left-1/2 transform -translate-x-1/2 -translate-y-1/2 bg-white rounded-lg p-6 max-w-2xl w-full z-50" overlayClassName="fixed inset-0 bg-black bg-opacity-50 z-40" > {problemId && ( <>
Problem Submitted: {problemId}
)} {!problemId && ( <>
Save prompts as new problems when AI results are unsatisfactory.
Problems are publicly visible and are used to improve AI performance.
Title:
Description:
Name (optional):
Email (optional):
)}
); });