diff --git a/apps/rowboat/app/globals.css b/apps/rowboat/app/globals.css index d72cd197..a95e1df0 100644 --- a/apps/rowboat/app/globals.css +++ b/apps/rowboat/app/globals.css @@ -163,3 +163,12 @@ body { .animate-float { animation: float 5s ease-in-out infinite, pulse-mascot 4s infinite; } + +/* Feedback modal textarea overrides */ +.feedback-modal textarea, +.feedback-modal textarea:focus { + font-size: 0.75rem !important; /* Tailwind's text-xs */ + box-shadow: none !important; + outline: none !important; + border-color: #d1d5db !important; /* Tailwind's gray-300 */ +} diff --git a/apps/rowboat/app/projects/[projectId]/playground/app.tsx b/apps/rowboat/app/projects/[projectId]/playground/app.tsx index 07082a16..9c49125e 100644 --- a/apps/rowboat/app/projects/[projectId]/playground/app.tsx +++ b/apps/rowboat/app/projects/[projectId]/playground/app.tsx @@ -26,6 +26,7 @@ export function App({ isInitialState = false, onPanelClick, projectTools, + triggerCopilotChat, }: { hidden?: boolean; projectId: string; @@ -36,6 +37,7 @@ export function App({ isInitialState?: boolean; onPanelClick?: () => void; projectTools: z.infer[]; + triggerCopilotChat?: (message: string) => void; }) { const [counter, setCounter] = useState(0); const [testProfile, setTestProfile] = useState> | null>(null); @@ -187,6 +189,7 @@ export function App({ onCopyClick={(fn) => { getCopyContentRef.current = fn; }} showDebugMessages={showDebugMessages} projectTools={projectTools} + triggerCopilotChat={triggerCopilotChat} /> diff --git a/apps/rowboat/app/projects/[projectId]/playground/components/chat.tsx b/apps/rowboat/app/projects/[projectId]/playground/components/chat.tsx index a69e730f..0bff978a 100644 --- a/apps/rowboat/app/projects/[projectId]/playground/components/chat.tsx +++ b/apps/rowboat/app/projects/[projectId]/playground/components/chat.tsx @@ -13,6 +13,8 @@ import { ProfileContextBox } from "./profile-context-box"; import { USE_TESTING_FEATURE } from "@/app/lib/feature_flags"; import { BillingUpgradeModal } from "@/components/common/billing-upgrade-modal"; import { ChevronDownIcon } from "@heroicons/react/24/outline"; +import { FeedbackModal } from "./feedback-modal"; +import { FIX_WORKFLOW_PROMPT, FIX_WORKFLOW_PROMPT_WITH_FEEDBACK } from "../copilot-prompts"; export function Chat({ chat, @@ -29,6 +31,7 @@ export function Chat({ showDebugMessages = true, showJsonMode = false, projectTools, + triggerCopilotChat, }: { chat: z.infer; projectId: string; @@ -44,6 +47,7 @@ export function Chat({ showDebugMessages?: boolean; showJsonMode?: boolean; projectTools: z.infer[]; + triggerCopilotChat?: (message: string) => void; }) { const [messages, setMessages] = useState[]>(chat.messages); const [loadingAssistantResponse, setLoadingAssistantResponse] = useState(false); @@ -53,6 +57,9 @@ export function Chat({ const [lastAgenticResponse, setLastAgenticResponse] = useState(null); const [optimisticMessages, setOptimisticMessages] = useState[]>(chat.messages); const [isLastInteracted, setIsLastInteracted] = useState(false); + const [showFeedbackModal, setShowFeedbackModal] = useState(false); + const [pendingFixMessage, setPendingFixMessage] = useState(null); + const [showSuccessMessage, setShowSuccessMessage] = useState(false); // --- Scroll/auto-scroll/unread bubble logic --- const scrollContainerRef = useRef(null); @@ -101,6 +108,37 @@ export function Chat({ setOptimisticMessages(messages); }, [messages]); + // Handle fix functionality + const handleFix = useCallback((message: string) => { + setPendingFixMessage(message); + setShowFeedbackModal(true); + }, []); + + const handleFeedbackSubmit = useCallback((feedback: string) => { + if (!pendingFixMessage) return; + + // Create the copilot prompt + const prompt = feedback.trim() + ? FIX_WORKFLOW_PROMPT_WITH_FEEDBACK + .replace('{chat_turn}', pendingFixMessage) + .replace('{feedback}', feedback) + : FIX_WORKFLOW_PROMPT + .replace('{chat_turn}', pendingFixMessage); + + // Use the triggerCopilotChat function if available, otherwise fall back to localStorage + if (triggerCopilotChat) { + triggerCopilotChat(prompt); + // Show a subtle success indication + setShowSuccessMessage(true); + setTimeout(() => setShowSuccessMessage(false), 3000); + } else { + // Fallback for standalone playground + localStorage.setItem(`project_prompt_${projectId}`, prompt); + alert('Fix request submitted! Redirecting to workflow editor...'); + window.location.href = `/projects/${projectId}/workflow`; + } + }, [pendingFixMessage, projectId, triggerCopilotChat]); + // collect published tool call results const toolCallResults: Record> = {}; optimisticMessages @@ -302,6 +340,7 @@ export function Chat({ showSystemMessage={false} showDebugMessages={showDebugMessages} showJsonMode={showJsonMode} + onFix={handleFix} /> {showUnreadBubble && ( + + )} {fetchResponseError && (
@@ -354,5 +406,11 @@ export function Chat({ onClose={() => setBillingError(null)} errorMessage={billingError || ''} /> + setShowFeedbackModal(false)} + onSubmit={handleFeedbackSubmit} + title="Fix Assistant" + />
; } \ No newline at end of file diff --git a/apps/rowboat/app/projects/[projectId]/playground/components/feedback-modal.tsx b/apps/rowboat/app/projects/[projectId]/playground/components/feedback-modal.tsx new file mode 100644 index 00000000..377ca8f3 --- /dev/null +++ b/apps/rowboat/app/projects/[projectId]/playground/components/feedback-modal.tsx @@ -0,0 +1,58 @@ +'use client'; +import { useState } from "react"; +import { Modal, ModalContent, ModalHeader, ModalBody, ModalFooter, Button, Textarea } from "@heroui/react"; + +interface FeedbackModalProps { + isOpen: boolean; + onClose: () => void; + onSubmit: (feedback: string) => void; + title?: string; +} + +export function FeedbackModal({ isOpen, onClose, onSubmit, title = "Provide Feedback" }: FeedbackModalProps) { + const [feedback, setFeedback] = useState(""); + + const handleSubmit = () => { + onSubmit(feedback); + setFeedback(""); + onClose(); + }; + + const handleCancel = () => { + setFeedback(""); + onClose(); + }; + + return ( + + + + {title} + +

+ Tell Skipper what needs to be fixed +

+ +
+