mirror of
https://github.com/stackblitz-labs/bolt.diy
synced 2025-06-26 18:26:38 +00:00
Add feedback button (#29)
This commit is contained in:
@@ -48,6 +48,18 @@ export function resetChatFileWritten() {
|
||||
}, 500);
|
||||
}
|
||||
|
||||
let gLastProjectContents: string | undefined;
|
||||
|
||||
export function getLastProjectContents() {
|
||||
return gLastProjectContents;
|
||||
}
|
||||
|
||||
let gLastChatMessages: Message[] | undefined;
|
||||
|
||||
export function getLastChatMessages() {
|
||||
return gLastChatMessages;
|
||||
}
|
||||
|
||||
async function flushSimulationData() {
|
||||
//console.log("FlushSimulationData");
|
||||
|
||||
@@ -427,6 +439,7 @@ export const ChatImpl = memo(
|
||||
if (lastMessage) {
|
||||
const { contentBase64 } = await workbenchStore.generateZipBase64();
|
||||
saveProjectContents(lastMessage.id, { content: contentBase64 });
|
||||
gLastProjectContents = contentBase64;
|
||||
}
|
||||
};
|
||||
|
||||
@@ -468,6 +481,19 @@ export const ChatImpl = memo(
|
||||
|
||||
const [messageRef, scrollRef] = useSnapScroll();
|
||||
|
||||
const chatMessages = messages.map((message, i) => {
|
||||
if (message.role === 'user') {
|
||||
return message;
|
||||
}
|
||||
|
||||
return {
|
||||
...message,
|
||||
content: parsedMessages[i] || '',
|
||||
};
|
||||
});
|
||||
|
||||
gLastChatMessages = chatMessages;
|
||||
|
||||
return (
|
||||
<BaseChat
|
||||
ref={animationScope}
|
||||
@@ -489,16 +515,7 @@ export const ChatImpl = memo(
|
||||
description={description}
|
||||
importChat={importChat}
|
||||
exportChat={exportChat}
|
||||
messages={messages.map((message, i) => {
|
||||
if (message.role === 'user') {
|
||||
return message;
|
||||
}
|
||||
|
||||
return {
|
||||
...message,
|
||||
content: parsedMessages[i] || '',
|
||||
};
|
||||
})}
|
||||
messages={chatMessages}
|
||||
enhancePrompt={() => {
|
||||
enhancePrompt(
|
||||
input,
|
||||
|
||||
131
app/components/header/Feedback.tsx
Normal file
131
app/components/header/Feedback.tsx
Normal file
@@ -0,0 +1,131 @@
|
||||
import { toast } from "react-toastify";
|
||||
import ReactModal from 'react-modal';
|
||||
import { useState } from "react";
|
||||
import { submitFeedback } from "~/lib/replay/Problems";
|
||||
import { getLastProjectContents, getLastChatMessages } from "../chat/Chat.client";
|
||||
|
||||
ReactModal.setAppElement('#root');
|
||||
|
||||
// Component for leaving feedback.
|
||||
|
||||
export function Feedback() {
|
||||
const [isModalOpen, setIsModalOpen] = useState(false);
|
||||
const [formData, setFormData] = useState({
|
||||
feedback: '',
|
||||
email: '',
|
||||
share: false
|
||||
});
|
||||
const [submitted, setSubmitted] = useState<boolean>(false);
|
||||
|
||||
const handleOpenModal = () => {
|
||||
setIsModalOpen(true);
|
||||
setFormData({
|
||||
feedback: '',
|
||||
email: '',
|
||||
share: false
|
||||
});
|
||||
setSubmitted(false);
|
||||
};
|
||||
|
||||
const handleSubmitFeedback = async () => {
|
||||
// Add validation here
|
||||
if (!formData.feedback) {
|
||||
toast.error('Please fill in feedback field');
|
||||
return;
|
||||
}
|
||||
|
||||
if (!formData.email) {
|
||||
toast.error('Please fill in email field');
|
||||
return;
|
||||
}
|
||||
|
||||
toast.info("Submitting feedback...");
|
||||
|
||||
console.log("SubmitFeedback", formData);
|
||||
|
||||
const feedbackData: any = {
|
||||
feedback: formData.feedback,
|
||||
email: formData.email,
|
||||
share: formData.share
|
||||
};
|
||||
|
||||
if (feedbackData.share) {
|
||||
// Note: We don't just use the workbench store here because wrangler generates a strange error.
|
||||
feedbackData.repositoryContents = getLastProjectContents();
|
||||
feedbackData.chatMessages = getLastChatMessages();
|
||||
}
|
||||
|
||||
await submitFeedback(feedbackData);
|
||||
setSubmitted(true);
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<a
|
||||
href="#"
|
||||
className="flex gap-2 bg-bolt-elements-sidebar-buttonBackgroundDefault text-bolt-elements-sidebar-buttonText hover:bg-bolt-elements-sidebar-buttonBackgroundHover rounded-md p-2 transition-theme"
|
||||
onClick={handleOpenModal}
|
||||
>
|
||||
Feedback
|
||||
</a>
|
||||
<ReactModal
|
||||
isOpen={isModalOpen}
|
||||
onRequestClose={() => 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"
|
||||
>
|
||||
{submitted && (
|
||||
<>
|
||||
<div className="text-center mb-2">Feedback Submitted</div>
|
||||
<div className="text-center">
|
||||
<div className="flex justify-center gap-2 mt-4">
|
||||
<button onClick={() => setIsModalOpen(false)} className="px-4 py-2 bg-gray-300 rounded hover:bg-gray-400">Close</button>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
{!submitted && (
|
||||
<>
|
||||
<div className="text-center">Let us know how Nut is doing.</div>
|
||||
<div className="flex items-center">Feedback:</div>
|
||||
<textarea
|
||||
name="feedback"
|
||||
className="bg-bolt-elements-background-depth-1 text-bolt-elements-textPrimary rounded px-2 w-full border border-gray-300"
|
||||
value={formData.feedback}
|
||||
onChange={(e) => setFormData(prev => ({
|
||||
...prev,
|
||||
feedback: e.target.value
|
||||
}))}
|
||||
/>
|
||||
<div className="flex items-center">Email:</div>
|
||||
<input type="text"
|
||||
name="email"
|
||||
className="bg-bolt-elements-background-depth-1 text-bolt-elements-textPrimary rounded px-2 w-full border border-gray-300"
|
||||
value={formData.email}
|
||||
onChange={(e) => setFormData(prev => ({
|
||||
...prev,
|
||||
email: e.target.value
|
||||
}))}
|
||||
/>
|
||||
<div className="flex items-center gap-2">
|
||||
<span>Share project with the Nut team:</span>
|
||||
<input type="checkbox"
|
||||
name="share"
|
||||
className="bg-bolt-elements-background-depth-1 text-bolt-elements-textPrimary rounded border border-gray-300"
|
||||
checked={formData.share}
|
||||
onChange={(e) => setFormData(prev => ({
|
||||
...prev,
|
||||
share: e.target.checked
|
||||
}))}
|
||||
/>
|
||||
</div>
|
||||
<div className="flex justify-center gap-2 mt-4">
|
||||
<button onClick={handleSubmitFeedback} className="px-4 py-2 bg-blue-500 text-white rounded hover:bg-blue-600">Submit</button>
|
||||
<button onClick={() => setIsModalOpen(false)} className="px-4 py-2 bg-gray-300 rounded hover:bg-gray-400">Cancel</button>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
</ReactModal>
|
||||
</>
|
||||
);
|
||||
}
|
||||
@@ -4,6 +4,7 @@ import { chatStore } from '~/lib/stores/chat';
|
||||
import { classNames } from '~/utils/classNames';
|
||||
import { HeaderActionButtons } from './HeaderActionButtons.client';
|
||||
import { ChatDescription } from '~/lib/persistence/ChatDescription.client';
|
||||
import { Feedback } from './Feedback';
|
||||
|
||||
export function Header() {
|
||||
const chat = useStore(chatStore);
|
||||
@@ -20,6 +21,7 @@ export function Header() {
|
||||
<a href="/" className="text-2xl font-semibold text-accent flex items-center">
|
||||
<img src="/logo-styled.svg" alt="logo" className="w-[40px] inline-block rotate-90" />
|
||||
</a>
|
||||
<Feedback />
|
||||
</div>
|
||||
{chat.started && ( // Display ChatDescription and HeaderActionButtons only when the chat has started.
|
||||
<>
|
||||
|
||||
@@ -11,7 +11,6 @@ import { logger } from '~/utils/logger';
|
||||
import { HistoryItem } from './HistoryItem';
|
||||
import { binDates } from './date-binning';
|
||||
import { useSearchFilter } from '~/lib/hooks/useSearchFilter';
|
||||
import ReactModal from 'react-modal';
|
||||
import { SaveProblem } from './SaveProblem';
|
||||
import { SaveSolution } from './SaveSolution';
|
||||
import { hasNutAdminKey } from '~/lib/replay/Problems';
|
||||
|
||||
@@ -5,7 +5,7 @@ import { workbenchStore } from "~/lib/stores/workbench";
|
||||
import { BoltProblemStatus, updateProblem } from "~/lib/replay/Problems";
|
||||
import type { BoltProblemInput } from "~/lib/replay/Problems";
|
||||
import { getLastLoadedProblem } from "../chat/LoadProblemButton";
|
||||
import { getLastUserSimulationData, getLastChatMessages } from "~/lib/replay/SimulationPrompt";
|
||||
import { getLastUserSimulationData, getLastSimulationChatMessages } from "~/lib/replay/SimulationPrompt";
|
||||
|
||||
ReactModal.setAppElement('#root');
|
||||
|
||||
@@ -48,7 +48,7 @@ export function SaveSolution() {
|
||||
return;
|
||||
}
|
||||
|
||||
const messages = getLastChatMessages();
|
||||
const messages = getLastSimulationChatMessages();
|
||||
if (!messages) {
|
||||
toast.error('No user prompt found');
|
||||
return;
|
||||
|
||||
@@ -166,3 +166,21 @@ export async function extractFileArtifactsFromRepositoryContents(repositoryConte
|
||||
}
|
||||
return fileArtifacts;
|
||||
}
|
||||
|
||||
export async function submitFeedback(feedback: any) {
|
||||
try {
|
||||
const rv = await sendCommandDedicatedClient({
|
||||
method: "Recording.globalExperimentalCommand",
|
||||
params: {
|
||||
name: "submitFeedback",
|
||||
params: { feedback },
|
||||
},
|
||||
});
|
||||
console.log("SubmitFeedbackRval", rv);
|
||||
return (rv as any).rval.problemId;
|
||||
} catch (error) {
|
||||
console.error("Error submitting feedback", error);
|
||||
toast.error("Failed to submit feedback");
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -199,10 +199,10 @@ export async function getSimulationRecording(): Promise<string> {
|
||||
return gChatManager.recordingIdPromise;
|
||||
}
|
||||
|
||||
let gLastChatMessages: ProtocolMessage[] | undefined;
|
||||
let gLastSimulationChatMessages: ProtocolMessage[] | undefined;
|
||||
|
||||
export function getLastChatMessages(): ProtocolMessage[] | undefined {
|
||||
return gLastChatMessages;
|
||||
export function getLastSimulationChatMessages(): ProtocolMessage[] | undefined {
|
||||
return gLastSimulationChatMessages;
|
||||
}
|
||||
|
||||
const SystemPrompt = `
|
||||
@@ -237,7 +237,7 @@ export async function getSimulationEnhancedPrompt(
|
||||
},
|
||||
];
|
||||
|
||||
gLastChatMessages = messages;
|
||||
gLastSimulationChatMessages = messages;
|
||||
|
||||
console.log("ChatSendMessage", new Date().toISOString(), JSON.stringify(messages));
|
||||
|
||||
|
||||
Reference in New Issue
Block a user