From 472fc4cb041351746e99d22d949cd1b6df4fb568 Mon Sep 17 00:00:00 2001 From: Alisha Augustin Date: Sun, 27 Oct 2024 22:08:27 -0700 Subject: [PATCH 1/4] added feedback page and placeholder for api endpoint --- gui/src/App.tsx | 167 ++++++++++------------- gui/src/components/Layout.tsx | 23 +++- gui/src/pages/feedback.tsx | 249 ++++++++++++++++++++++++++++++++++ 3 files changed, 338 insertions(+), 101 deletions(-) create mode 100644 gui/src/pages/feedback.tsx diff --git a/gui/src/App.tsx b/gui/src/App.tsx index cd46843fe89..c1ab1e706cd 100644 --- a/gui/src/App.tsx +++ b/gui/src/App.tsx @@ -19,106 +19,85 @@ import LocalOnboarding from "./pages/onboarding/LocalOnboarding"; import Onboarding from "./pages/onboarding/Onboarding"; import SettingsPage from "./pages/settings"; import Stats from "./pages/stats"; -import Inventory from "./pages/inventory"; -import AiderGUI from "./integrations/aider/aidergui"; -import PerplexityGUI from "./integrations/perplexity/perplexitygui"; +import Feedback from "./pages/feedback" - -declare global { - interface Window { - initialRoute?: string; - } -} - -const router = createMemoryRouter( - [ - { - path: "/", - element: , - errorElement: , - children: [ - { - path: "/index.html", - element: , - }, - { - path: "/", - element: , - }, - { - path: "/aiderMode", - element: , - }, - { - path: "/perplexityMode", - element: , - }, - { - path: "/history", - element: , - }, - { - path: "/stats", - element: , - }, - { - path: "/help", - element: , - }, - { - path: "/settings", - element: , - }, - { - path: "/addModel", - element: , - }, - { - path: "/addModel/provider/:providerName", - element: , - }, - { - path: "/help", - element: , - }, - { - path: "/monaco", - element: , - }, - { - path: "/onboarding", - element: , - }, - { - path: "/localOnboarding", - element: , - }, - { - path: "/migration", - element: , - }, - { - path: "/apiKeysOnboarding", - element: , - }, - { - path: "/apiKeyAutocompleteOnboarding", - element: , - }, - { - path: "/inventory/*", - element: , - }, - ], - }, - ], +const router = createMemoryRouter([ { - initialEntries: [window.isPearOverlay ? "/inventory" : window.initialRoute], + path: "/", + element: , + errorElement: , + children: [ + { + path: "/index.html", + element: , + }, + { + path: "/", + element: , + }, + { + path: "/history", + element: , + }, + { + path: "/stats", + element: , + }, + { + path: "/help", + element: , + }, + { + path: "/settings", + element: , + }, + { + path: "/addModel", + element: , + }, + { + path: "/addModel/provider/:providerName", + element: , + }, + { + path: "/help", + element: , + }, + { + path: "/monaco", + element: , + }, + { + path: "/onboarding", + element: , + }, + { + path: "/localOnboarding", + element: , + }, + { + path: "/migration", + element: , + }, + { + path: "/apiKeysOnboarding", + element: , + }, + { + path: "/apiKeyAutocompleteOnboarding", + element: , + }, + { + path: "/feedback", + element: , + }, + ], }, -); +]); function App() { const dispatch = useDispatch(); + useSetup(dispatch); const vscTheme = useVscTheme(); diff --git a/gui/src/components/Layout.tsx b/gui/src/components/Layout.tsx index 32c652cc936..655d19cafcb 100644 --- a/gui/src/components/Layout.tsx +++ b/gui/src/components/Layout.tsx @@ -1,4 +1,4 @@ -import { QuestionMarkCircleIcon } from "@heroicons/react/24/outline"; +import { QuestionMarkCircleIcon, InboxStackIcon } from "@heroicons/react/24/outline"; import { IndexingProgressUpdate } from "core"; import { useContext, useEffect, useState, useRef } from "react"; import { useDispatch, useSelector } from "react-redux"; @@ -118,10 +118,6 @@ const HIDE_FOOTER_ON_PAGES = [ "/onboarding", "/localOnboarding", "/apiKeyOnboarding", - "/aiderMode", - "/inventory", - "/inventory/aiderMode", - "/inventory/perplexityMode" ]; const SHOW_SHORTCUTS_ON_PAGES = ["/"]; @@ -280,9 +276,9 @@ const Layout = () => { /> - {SHOW_SHORTCUTS_ON_PAGES.includes(location.pathname) && !window.isPearOverlay && ( + {SHOW_SHORTCUTS_ON_PAGES.includes(location.pathname) && (
@@ -318,6 +314,19 @@ const Layout = () => { > + { + if (location.pathname === "/feedback") { + navigate("/"); + } else { + navigate("/feedback"); + } + }} + > + + )}
diff --git a/gui/src/pages/feedback.tsx b/gui/src/pages/feedback.tsx new file mode 100644 index 00000000000..ce80b08e319 --- /dev/null +++ b/gui/src/pages/feedback.tsx @@ -0,0 +1,249 @@ +/* eslint-disable header/header */ +import { useState, useEffect } from 'react'; +import { Button, vscEditorBackground, vscBackground, vscInputBackground, vscForeground } from '../components'; +import styled from 'styled-components'; + +const GridDiv = styled.div` + display: grid; + padding: 2rem; + justify-items: center; + align-items: center; + overflow-y: auto; +`; + +const Input = styled.input` + width: 100%; + padding: 8px; + margin: 4px 0; + background-color: ${vscInputBackground}; + color: ${vscForeground}; + border: 1px solid ${vscInputBackground}; + border-radius: 4px; + &:focus { + outline: none; + border-color: #007acc; + } +`; + +const Select = styled.select` + width: 100%; + padding: 8px; + margin: 4px 0; + background-color: ${vscInputBackground}; + color: ${vscForeground}; + border: 1px solid ${vscInputBackground}; + border-radius: 4px; + &:focus { + outline: none; + border-color: #007acc; + } +`; + +const TextArea = styled.textarea` + width: 100%; + padding: 8px; + margin: 4px 0; + background-color: ${vscInputBackground}; + color: ${vscForeground}; + border: 1px solid ${vscInputBackground}; + border-radius: 4px; + &:focus { + outline: none; + border-color: #007acc; + } +`; + +const FormGroup = styled.div` + margin-bottom: 1rem; + width: 100%; +`; + +const StyledLink = styled(Button).attrs({ + as: 'a', + })<{ href: string; target?: string }>` + white-space: nowrap; + text-decoration: none; + + &:hover { + color: inherit; + text-decoration: none; + } + + @media (max-width: 400px) { + .icon { + display: none; + } + `; + +const Label = styled.label` + display: block; + margin-bottom: 4px; + color: ${vscForeground}; +`; +function Feedback({ onClose }: { onClose?: () => void }) { + const [formData, setFormData] = useState({ + name: '', + email: '', + isSubscribed: 'no', + subscriptionType: 'none', + operatingSystem: '', + feedback: '' + }); + + // Detect OS on component mount + useEffect(() => { + const platform = window.navigator.platform.toLowerCase(); + const userAgent = window.navigator.userAgent.toLowerCase(); + + let detectedOS = ''; + if (platform.includes('linux')) { + detectedOS = 'Linux'; + } else if (platform.includes('win')) { + detectedOS = 'Windows'; + } else if (platform.includes('mac')) { + if (userAgent.includes('mac') && userAgent.includes('arm64')) { + detectedOS = 'M-chip Mac'; + } else { + detectedOS = 'Intel Mac'; + } + } + + setFormData(prev => ({ + ...prev, + operatingSystem: detectedOS + })); + }, []); + + const handleChange = (e) => { + const { name, value } = e.target; + setFormData(prev => ({ + ...prev, + [name]: value, + ...(name === 'isSubscribed' && value === 'no' ? { subscriptionType: 'none' } : {}) + })); + }; + + const handleSubmit = async (e) => { + e.preventDefault(); + + if (!formData.feedback.trim()) { + alert('Please enter your feedback before submitting.'); + return; + } + + const emailSubject = 'PearAI Feedback'; + const emailBody = ` + Feedback: + ${formData.feedback} + + User Info: + Name: ${formData.name} + Email: ${formData.email} + Subscription Status: ${formData.isSubscribed} + Subscription Type: ${formData.subscriptionType} + Operating System: ${formData.operatingSystem} + `.trim(); + + await fetch('/api/send-feedback', { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify(formData) + }); + + }; + + return ( +
+ +

Send Feedback

+

Help us improve PearAI by sharing your thoughts

+ +
+ + + + + + + + + + + + + + + + {formData.isSubscribed === 'yes' && ( + + + + + )} + + + + + + + + +