Upload 11 files
Browse files- .env.local +1 -0
- .gitignore +24 -0
- App.tsx +99 -0
- README.md +14 -3
- index.html +49 -0
- index.tsx +16 -0
- metadata.json +7 -0
- package.json +21 -0
- tsconfig.json +30 -0
- types.ts +14 -0
- vite.config.ts +17 -0
.env.local
ADDED
|
@@ -0,0 +1 @@
|
|
|
|
|
|
|
| 1 |
+
GEMINI_API_KEY=PLACEHOLDER_API_KEY
|
.gitignore
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Logs
|
| 2 |
+
logs
|
| 3 |
+
*.log
|
| 4 |
+
npm-debug.log*
|
| 5 |
+
yarn-debug.log*
|
| 6 |
+
yarn-error.log*
|
| 7 |
+
pnpm-debug.log*
|
| 8 |
+
lerna-debug.log*
|
| 9 |
+
|
| 10 |
+
node_modules
|
| 11 |
+
dist
|
| 12 |
+
dist-ssr
|
| 13 |
+
*.local
|
| 14 |
+
|
| 15 |
+
# Editor directories and files
|
| 16 |
+
.vscode/*
|
| 17 |
+
!.vscode/extensions.json
|
| 18 |
+
.idea
|
| 19 |
+
.DS_Store
|
| 20 |
+
*.suo
|
| 21 |
+
*.ntvs*
|
| 22 |
+
*.njsproj
|
| 23 |
+
*.sln
|
| 24 |
+
*.sw?
|
App.tsx
ADDED
|
@@ -0,0 +1,99 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
|
| 2 |
+
import React, { useState, useEffect } from 'react';
|
| 3 |
+
import ChatInterface from './components/ChatInterface';
|
| 4 |
+
import { LanguageProvider } from './contexts/LanguageContext';
|
| 5 |
+
import LanguageSelector from './components/LanguageSelector';
|
| 6 |
+
import FeedbackPage from './components/FeedbackPage';
|
| 7 |
+
import UserFeedbackAnalysisPage from './components/UserFeedbackAnalysisPage';
|
| 8 |
+
import { Review } from './types';
|
| 9 |
+
|
| 10 |
+
function App() {
|
| 11 |
+
const [page, setPage] = useState<'chat' | 'feedback' | 'userexperience'>('chat');
|
| 12 |
+
|
| 13 |
+
// Initialize reviews from localStorage
|
| 14 |
+
const [reviews, setReviews] = useState<Review[]>(() => {
|
| 15 |
+
try {
|
| 16 |
+
const storedReviews = window.localStorage.getItem('emotimate-reviews');
|
| 17 |
+
return storedReviews ? JSON.parse(storedReviews) : [];
|
| 18 |
+
} catch (error) {
|
| 19 |
+
console.error("Error reading reviews from localStorage", error);
|
| 20 |
+
return [];
|
| 21 |
+
}
|
| 22 |
+
});
|
| 23 |
+
|
| 24 |
+
// Save reviews to localStorage whenever they change
|
| 25 |
+
useEffect(() => {
|
| 26 |
+
try {
|
| 27 |
+
window.localStorage.setItem('emotimate-reviews', JSON.stringify(reviews));
|
| 28 |
+
} catch (error) {
|
| 29 |
+
console.error("Error saving reviews to localStorage", error);
|
| 30 |
+
}
|
| 31 |
+
}, [reviews]);
|
| 32 |
+
|
| 33 |
+
const handleAddReview = (review: Omit<Review, 'id' | 'date'>) => {
|
| 34 |
+
const newReview: Review = {
|
| 35 |
+
...review,
|
| 36 |
+
id: `User-${Math.random().toString(36).substr(2, 4).toUpperCase()}`,
|
| 37 |
+
date: new Date().toLocaleDateString('en-US', { month: 'short', day: 'numeric', year: 'numeric' }),
|
| 38 |
+
};
|
| 39 |
+
setReviews(prev => [...prev, newReview]);
|
| 40 |
+
};
|
| 41 |
+
|
| 42 |
+
const handleDeleteReview = (reviewId: string) => {
|
| 43 |
+
setReviews(prev => prev.filter(review => review.id !== reviewId));
|
| 44 |
+
};
|
| 45 |
+
|
| 46 |
+
const buttonClasses = "text-sm sm:text-base text-slate-600 hover:text-blue-600 font-medium transition-colors px-3 py-2 rounded-lg hover:bg-slate-200";
|
| 47 |
+
|
| 48 |
+
return (
|
| 49 |
+
<LanguageProvider>
|
| 50 |
+
<div className="flex flex-col h-screen bg-slate-100 font-sans">
|
| 51 |
+
<header className="w-full bg-white shadow-md px-4 py-3 sm:p-4 z-10">
|
| 52 |
+
<div className="max-w-4xl mx-auto flex justify-between items-center">
|
| 53 |
+
<h1 className="text-xl sm:text-2xl font-bold bg-gradient-to-r from-blue-500 to-violet-500 text-transparent bg-clip-text">EmotiMate</h1>
|
| 54 |
+
<div className="flex items-center gap-2 sm:gap-4">
|
| 55 |
+
{page === 'chat' && (
|
| 56 |
+
<>
|
| 57 |
+
<LanguageSelector />
|
| 58 |
+
<button onClick={() => setPage('feedback')} className={buttonClasses} aria-label="Go to feedback page">
|
| 59 |
+
Feedback
|
| 60 |
+
</button>
|
| 61 |
+
<button onClick={() => setPage('userexperience')} className={buttonClasses} aria-label="Go to user experience page">
|
| 62 |
+
User Experience
|
| 63 |
+
</button>
|
| 64 |
+
</>
|
| 65 |
+
)}
|
| 66 |
+
{page === 'feedback' && (
|
| 67 |
+
<>
|
| 68 |
+
<button onClick={() => setPage('userexperience')} className={buttonClasses} aria-label="Go to user experience page">
|
| 69 |
+
User Experience
|
| 70 |
+
</button>
|
| 71 |
+
<button onClick={() => setPage('chat')} className={buttonClasses} aria-label="Go back to home page">
|
| 72 |
+
Home
|
| 73 |
+
</button>
|
| 74 |
+
</>
|
| 75 |
+
)}
|
| 76 |
+
{page === 'userexperience' && (
|
| 77 |
+
<>
|
| 78 |
+
<button onClick={() => setPage('feedback')} className={buttonClasses} aria-label="Go to feedback page">
|
| 79 |
+
Feedback
|
| 80 |
+
</button>
|
| 81 |
+
<button onClick={() => setPage('chat')} className={buttonClasses} aria-label="Go back to home page">
|
| 82 |
+
Home
|
| 83 |
+
</button>
|
| 84 |
+
</>
|
| 85 |
+
)}
|
| 86 |
+
</div>
|
| 87 |
+
</div>
|
| 88 |
+
</header>
|
| 89 |
+
<main className="flex-grow overflow-auto custom-scrollbar">
|
| 90 |
+
{page === 'chat' && <ChatInterface />}
|
| 91 |
+
{page === 'feedback' && <FeedbackPage onGoHome={() => setPage('chat')} onAddReview={handleAddReview} />}
|
| 92 |
+
{page === 'userexperience' && <UserFeedbackAnalysisPage reviews={reviews} onDeleteReview={handleDeleteReview} />}
|
| 93 |
+
</main>
|
| 94 |
+
</div>
|
| 95 |
+
</LanguageProvider>
|
| 96 |
+
);
|
| 97 |
+
}
|
| 98 |
+
|
| 99 |
+
export default App;
|
README.md
CHANGED
|
@@ -1,3 +1,14 @@
|
|
| 1 |
-
|
| 2 |
-
|
| 3 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Run and deploy your AI Studio app
|
| 2 |
+
|
| 3 |
+
This contains everything you need to run your app locally.
|
| 4 |
+
|
| 5 |
+
## Run Locally
|
| 6 |
+
|
| 7 |
+
**Prerequisites:** Node.js
|
| 8 |
+
|
| 9 |
+
|
| 10 |
+
1. Install dependencies:
|
| 11 |
+
`npm install`
|
| 12 |
+
2. Set the `GEMINI_API_KEY` in [.env.local](.env.local) to your Gemini API key
|
| 13 |
+
3. Run the app:
|
| 14 |
+
`npm run dev`
|
index.html
ADDED
|
@@ -0,0 +1,49 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
<!DOCTYPE html>
|
| 2 |
+
<html lang="en">
|
| 3 |
+
<head>
|
| 4 |
+
<meta charset="UTF-8" />
|
| 5 |
+
<link rel="icon" type="image/svg+xml" href="/favicon.svg" />
|
| 6 |
+
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
| 7 |
+
<title>EmotiMate</title>
|
| 8 |
+
<script src="https://cdn.tailwindcss.com"></script>
|
| 9 |
+
<style>
|
| 10 |
+
/* For custom scrollbar styling */
|
| 11 |
+
.custom-scrollbar::-webkit-scrollbar {
|
| 12 |
+
width: 8px;
|
| 13 |
+
}
|
| 14 |
+
.custom-scrollbar::-webkit-scrollbar-track {
|
| 15 |
+
background: #f1f5f9; /* slate-100 */
|
| 16 |
+
}
|
| 17 |
+
.custom-scrollbar::-webkit-scrollbar-thumb {
|
| 18 |
+
background: #94a3b8; /* slate-400 */
|
| 19 |
+
border-radius: 10px;
|
| 20 |
+
}
|
| 21 |
+
.custom-scrollbar::-webkit-scrollbar-thumb:hover {
|
| 22 |
+
background: #64748b; /* slate-500 */
|
| 23 |
+
}
|
| 24 |
+
/* Utility to permanently hide scrollbars */
|
| 25 |
+
.hide-scrollbar::-webkit-scrollbar {
|
| 26 |
+
display: none;
|
| 27 |
+
}
|
| 28 |
+
.hide-scrollbar {
|
| 29 |
+
-ms-overflow-style: none; /* IE and Edge */
|
| 30 |
+
scrollbar-width: none; /* Firefox */
|
| 31 |
+
}
|
| 32 |
+
</style>
|
| 33 |
+
<script type="importmap">
|
| 34 |
+
{
|
| 35 |
+
"imports": {
|
| 36 |
+
"react-dom/": "https://esm.sh/react-dom@^19.1.0/",
|
| 37 |
+
"react/": "https://esm.sh/react@^19.1.0/",
|
| 38 |
+
"react": "https://esm.sh/react@^19.1.0",
|
| 39 |
+
"@google/genai": "https://esm.sh/@google/genai@^1.11.0"
|
| 40 |
+
}
|
| 41 |
+
}
|
| 42 |
+
</script>
|
| 43 |
+
<link rel="stylesheet" href="/index.css">
|
| 44 |
+
</head>
|
| 45 |
+
<body class="bg-slate-100">
|
| 46 |
+
<div id="root"></div>
|
| 47 |
+
<script type="module" src="/index.tsx"></script>
|
| 48 |
+
</body>
|
| 49 |
+
</html>
|
index.tsx
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
|
| 2 |
+
import React from 'react';
|
| 3 |
+
import ReactDOM from 'react-dom/client';
|
| 4 |
+
import App from './App';
|
| 5 |
+
|
| 6 |
+
const rootElement = document.getElementById('root');
|
| 7 |
+
if (!rootElement) {
|
| 8 |
+
throw new Error("Could not find root element to mount to");
|
| 9 |
+
}
|
| 10 |
+
|
| 11 |
+
const root = ReactDOM.createRoot(rootElement);
|
| 12 |
+
root.render(
|
| 13 |
+
<React.StrictMode>
|
| 14 |
+
<App />
|
| 15 |
+
</React.StrictMode>
|
| 16 |
+
);
|
metadata.json
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"name": "EmotiMate",
|
| 3 |
+
"description": "An emotionally intelligent AI assistant for supportive and empathetic conversations. Engage via voice or text to receive supportive suggestions and mindfulness exercises in a safe, non-judgmental space.",
|
| 4 |
+
"requestFramePermissions": [
|
| 5 |
+
"microphone"
|
| 6 |
+
]
|
| 7 |
+
}
|
package.json
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"name": "emotimate",
|
| 3 |
+
"private": true,
|
| 4 |
+
"version": "0.0.0",
|
| 5 |
+
"type": "module",
|
| 6 |
+
"scripts": {
|
| 7 |
+
"dev": "vite",
|
| 8 |
+
"build": "vite build",
|
| 9 |
+
"preview": "vite preview"
|
| 10 |
+
},
|
| 11 |
+
"dependencies": {
|
| 12 |
+
"react-dom": "^19.1.0",
|
| 13 |
+
"react": "^19.1.0",
|
| 14 |
+
"@google/genai": "^1.11.0"
|
| 15 |
+
},
|
| 16 |
+
"devDependencies": {
|
| 17 |
+
"@types/node": "^22.14.0",
|
| 18 |
+
"typescript": "~5.7.2",
|
| 19 |
+
"vite": "^6.2.0"
|
| 20 |
+
}
|
| 21 |
+
}
|
tsconfig.json
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"compilerOptions": {
|
| 3 |
+
"target": "ES2020",
|
| 4 |
+
"experimentalDecorators": true,
|
| 5 |
+
"useDefineForClassFields": false,
|
| 6 |
+
"module": "ESNext",
|
| 7 |
+
"lib": ["ES2020", "DOM", "DOM.Iterable"],
|
| 8 |
+
"skipLibCheck": true,
|
| 9 |
+
|
| 10 |
+
/* Bundler mode */
|
| 11 |
+
"moduleResolution": "bundler",
|
| 12 |
+
"allowImportingTsExtensions": true,
|
| 13 |
+
"isolatedModules": true,
|
| 14 |
+
"moduleDetection": "force",
|
| 15 |
+
"noEmit": true,
|
| 16 |
+
"allowJs": true,
|
| 17 |
+
"jsx": "react-jsx",
|
| 18 |
+
|
| 19 |
+
/* Linting */
|
| 20 |
+
"strict": true,
|
| 21 |
+
"noUnusedLocals": true,
|
| 22 |
+
"noUnusedParameters": true,
|
| 23 |
+
"noFallthroughCasesInSwitch": true,
|
| 24 |
+
"noUncheckedSideEffectImports": true,
|
| 25 |
+
|
| 26 |
+
"paths": {
|
| 27 |
+
"@/*" : ["./*"]
|
| 28 |
+
}
|
| 29 |
+
}
|
| 30 |
+
}
|
types.ts
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
export interface Message {
|
| 2 |
+
id: string;
|
| 3 |
+
text: string;
|
| 4 |
+
sender: 'user' | 'ai';
|
| 5 |
+
suggestions?: string[];
|
| 6 |
+
}
|
| 7 |
+
|
| 8 |
+
export interface Review {
|
| 9 |
+
id: string;
|
| 10 |
+
date: string;
|
| 11 |
+
lang: string;
|
| 12 |
+
rating: number;
|
| 13 |
+
comments: string;
|
| 14 |
+
}
|
vite.config.ts
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import path from 'path';
|
| 2 |
+
import { defineConfig, loadEnv } from 'vite';
|
| 3 |
+
|
| 4 |
+
export default defineConfig(({ mode }) => {
|
| 5 |
+
const env = loadEnv(mode, '.', '');
|
| 6 |
+
return {
|
| 7 |
+
define: {
|
| 8 |
+
'process.env.API_KEY': JSON.stringify(env.GEMINI_API_KEY),
|
| 9 |
+
'process.env.GEMINI_API_KEY': JSON.stringify(env.GEMINI_API_KEY)
|
| 10 |
+
},
|
| 11 |
+
resolve: {
|
| 12 |
+
alias: {
|
| 13 |
+
'@': path.resolve(__dirname, '.'),
|
| 14 |
+
}
|
| 15 |
+
}
|
| 16 |
+
};
|
| 17 |
+
});
|