Tina Tarighian
commited on
Commit
Β·
128b839
1
Parent(s):
3fdfac3
mobile updates
Browse files- components/CameraSetup.js +5 -6
- components/MainContent.js +19 -6
- components/PromptEditor.js +84 -58
- components/ThoughtBubble.js +2 -2
- components/UIUtils.js +5 -0
- pages/_document.js +6 -1
components/CameraSetup.js
CHANGED
@@ -179,14 +179,13 @@ const CameraSetup = ({
|
|
179 |
{isMobile && (
|
180 |
<button
|
181 |
onClick={switchCamera}
|
182 |
-
className="absolute top-4 right-4 bg-white bg-opacity-70 p-2 rounded-full shadow-md z-10 hover:bg-opacity-90 transition-all"
|
183 |
aria-label={`Switch to ${facingMode === 'user' ? 'back' : 'front'} camera`}
|
|
|
184 |
>
|
185 |
-
<
|
186 |
-
|
187 |
-
|
188 |
-
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M14.5 7.5L9.5 12.5" />
|
189 |
-
</svg>
|
190 |
</button>
|
191 |
)}
|
192 |
</>
|
|
|
179 |
{isMobile && (
|
180 |
<button
|
181 |
onClick={switchCamera}
|
182 |
+
className="absolute top-4 right-4 bg-white bg-opacity-70 p-2 rounded-full shadow-md z-10 hover:bg-opacity-90 transition-all flex items-center justify-center"
|
183 |
aria-label={`Switch to ${facingMode === 'user' ? 'back' : 'front'} camera`}
|
184 |
+
style={{ width: '40px', height: '40px' }}
|
185 |
>
|
186 |
+
<span className="material-symbols-outlined text-gray-800" style={{ display: 'flex', lineHeight: 1 }}>
|
187 |
+
cameraswitch
|
188 |
+
</span>
|
|
|
|
|
189 |
</button>
|
190 |
)}
|
191 |
</>
|
components/MainContent.js
CHANGED
@@ -35,12 +35,15 @@ const MainContent = ({
|
|
35 |
}) => {
|
36 |
return (
|
37 |
<>
|
38 |
-
{/* Prompt Editor */}
|
39 |
-
|
40 |
-
|
41 |
-
|
42 |
-
|
43 |
-
|
|
|
|
|
|
|
44 |
|
45 |
<div ref={containerRef} className="relative w-full max-w-4xl canvas-container">
|
46 |
{/* Camera Setup */}
|
@@ -90,6 +93,16 @@ const MainContent = ({
|
|
90 |
createPopParticles={createPopParticles}
|
91 |
/>
|
92 |
</div>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
93 |
</>
|
94 |
);
|
95 |
};
|
|
|
35 |
}) => {
|
36 |
return (
|
37 |
<>
|
38 |
+
{/* Prompt Editor - Only show above camera on desktop */}
|
39 |
+
{!isMobile && (
|
40 |
+
<PromptEditor
|
41 |
+
customPrompt={customPrompt}
|
42 |
+
setCustomPrompt={setCustomPrompt}
|
43 |
+
isMac={isMac}
|
44 |
+
isMobile={isMobile}
|
45 |
+
/>
|
46 |
+
)}
|
47 |
|
48 |
<div ref={containerRef} className="relative w-full max-w-4xl canvas-container">
|
49 |
{/* Camera Setup */}
|
|
|
93 |
createPopParticles={createPopParticles}
|
94 |
/>
|
95 |
</div>
|
96 |
+
|
97 |
+
{/* Prompt Editor - Show below camera on mobile */}
|
98 |
+
{isMobile && (
|
99 |
+
<PromptEditor
|
100 |
+
customPrompt={customPrompt}
|
101 |
+
setCustomPrompt={setCustomPrompt}
|
102 |
+
isMac={isMac}
|
103 |
+
isMobile={isMobile}
|
104 |
+
/>
|
105 |
+
)}
|
106 |
</>
|
107 |
);
|
108 |
};
|
components/PromptEditor.js
CHANGED
@@ -1,74 +1,100 @@
|
|
1 |
-
import { useState } from 'react';
|
2 |
|
3 |
-
const PromptEditor = ({ customPrompt, setCustomPrompt, isMac }) => {
|
4 |
const [isEditingPrompt, setIsEditingPrompt] = useState(false);
|
5 |
const [tempPrompt, setTempPrompt] = useState("");
|
|
|
|
|
|
|
|
|
|
|
|
|
6 |
|
7 |
return (
|
8 |
-
<div className=
|
9 |
-
<div
|
|
|
|
|
|
|
10 |
<label htmlFor="system-prompt" className="block text-sm font-medium text-gray-700">
|
11 |
System Prompt
|
12 |
</label>
|
13 |
-
|
14 |
-
|
15 |
-
onClick={() => {
|
16 |
-
setTempPrompt(customPrompt);
|
17 |
-
setIsEditingPrompt(true);
|
18 |
-
}}
|
19 |
-
className="text-sm text-blue-600 hover:text-blue-800 flex items-center"
|
20 |
-
>
|
21 |
-
<svg xmlns="http://www.w3.org/2000/svg" className="h-4 w-4 mr-1" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
22 |
-
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M15.232 5.232l3.536 3.536m-2.036-5.036a2.5 2.5 0 113.536 3.536L6.5 21.036H3v-3.572L16.732 3.732z" />
|
23 |
-
</svg>
|
24 |
-
Edit
|
25 |
-
</button>
|
26 |
-
) : null}
|
27 |
-
</div>
|
28 |
-
|
29 |
-
{isEditingPrompt ? (
|
30 |
-
<div>
|
31 |
-
<textarea
|
32 |
-
id="system-prompt"
|
33 |
-
value={tempPrompt}
|
34 |
-
onChange={(e) => setTempPrompt(e.target.value)}
|
35 |
-
onKeyDown={(e) => {
|
36 |
-
// Save on Ctrl+Enter or Cmd+Enter
|
37 |
-
if ((e.ctrlKey || e.metaKey) && e.key === 'Enter') {
|
38 |
-
e.preventDefault();
|
39 |
-
setCustomPrompt(tempPrompt);
|
40 |
-
setIsEditingPrompt(false);
|
41 |
-
}
|
42 |
-
}}
|
43 |
-
className="w-full p-3 border border-gray-300 rounded-lg shadow-sm focus:ring-blue-500 focus:border-blue-500 text-sm"
|
44 |
-
rows={4}
|
45 |
-
placeholder="Enter your custom prompt for Gemini..."
|
46 |
-
/>
|
47 |
-
<div className="flex justify-end mt-2 space-x-2">
|
48 |
-
<span className="text-xs text-gray-500 self-center mr-auto">
|
49 |
-
Tip: Press {isMac ? 'β' : 'Ctrl'}+Enter to save
|
50 |
-
</span>
|
51 |
<button
|
52 |
-
onClick={() =>
|
53 |
-
|
54 |
-
|
55 |
-
|
56 |
-
</button>
|
57 |
-
<button
|
58 |
-
onClick={() => {
|
59 |
-
setCustomPrompt(tempPrompt);
|
60 |
-
setIsEditingPrompt(false);
|
61 |
}}
|
62 |
-
className="
|
63 |
>
|
64 |
-
|
|
|
|
|
|
|
65 |
</button>
|
66 |
-
|
67 |
-
|
68 |
-
|
69 |
-
|
70 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
71 |
</div>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
72 |
)}
|
73 |
</div>
|
74 |
);
|
|
|
1 |
+
import { useState, useEffect } from 'react';
|
2 |
|
3 |
+
const PromptEditor = ({ customPrompt, setCustomPrompt, isMac, isMobile }) => {
|
4 |
const [isEditingPrompt, setIsEditingPrompt] = useState(false);
|
5 |
const [tempPrompt, setTempPrompt] = useState("");
|
6 |
+
const [isCollapsed, setIsCollapsed] = useState(isMobile); // Collapsed by default on mobile
|
7 |
+
|
8 |
+
// Update collapsed state if isMobile changes
|
9 |
+
useEffect(() => {
|
10 |
+
setIsCollapsed(isMobile);
|
11 |
+
}, [isMobile]);
|
12 |
|
13 |
return (
|
14 |
+
<div className={`w-full max-w-4xl ${isMobile ? 'mt-4' : 'mb-4'}`}>
|
15 |
+
<div
|
16 |
+
className="flex justify-between items-center mb-1 cursor-pointer"
|
17 |
+
onClick={() => setIsCollapsed(!isCollapsed)}
|
18 |
+
>
|
19 |
<label htmlFor="system-prompt" className="block text-sm font-medium text-gray-700">
|
20 |
System Prompt
|
21 |
</label>
|
22 |
+
<div className="flex items-center">
|
23 |
+
{!isEditingPrompt && !isCollapsed && (
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
24 |
<button
|
25 |
+
onClick={(e) => {
|
26 |
+
e.stopPropagation(); // Prevent triggering the parent onClick
|
27 |
+
setTempPrompt(customPrompt);
|
28 |
+
setIsEditingPrompt(true);
|
|
|
|
|
|
|
|
|
|
|
29 |
}}
|
30 |
+
className="text-sm text-blue-600 hover:text-blue-800 flex items-center mr-2"
|
31 |
>
|
32 |
+
<svg xmlns="http://www.w3.org/2000/svg" className="h-4 w-4 mr-1" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
33 |
+
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M15.232 5.232l3.536 3.536m-2.036-5.036a2.5 2.5 0 113.536 3.536L6.5 21.036H3v-3.572L16.732 3.732z" />
|
34 |
+
</svg>
|
35 |
+
Edit
|
36 |
</button>
|
37 |
+
)}
|
38 |
+
{/* Down/Up caret icon */}
|
39 |
+
<svg
|
40 |
+
xmlns="http://www.w3.org/2000/svg"
|
41 |
+
className={`h-5 w-5 text-gray-500 transition-transform duration-200 ${isCollapsed ? '' : 'transform rotate-180'}`}
|
42 |
+
fill="none"
|
43 |
+
viewBox="0 0 24 24"
|
44 |
+
stroke="currentColor"
|
45 |
+
>
|
46 |
+
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M19 9l-7 7-7-7" />
|
47 |
+
</svg>
|
48 |
</div>
|
49 |
+
</div>
|
50 |
+
|
51 |
+
{!isCollapsed && (
|
52 |
+
<>
|
53 |
+
{isEditingPrompt ? (
|
54 |
+
<div>
|
55 |
+
<textarea
|
56 |
+
id="system-prompt"
|
57 |
+
value={tempPrompt}
|
58 |
+
onChange={(e) => setTempPrompt(e.target.value)}
|
59 |
+
onKeyDown={(e) => {
|
60 |
+
// Save on Ctrl+Enter or Cmd+Enter
|
61 |
+
if ((e.ctrlKey || e.metaKey) && e.key === 'Enter') {
|
62 |
+
e.preventDefault();
|
63 |
+
setCustomPrompt(tempPrompt);
|
64 |
+
setIsEditingPrompt(false);
|
65 |
+
}
|
66 |
+
}}
|
67 |
+
className="w-full p-3 border border-gray-300 rounded-lg shadow-sm focus:ring-blue-500 focus:border-blue-500 text-sm"
|
68 |
+
rows={4}
|
69 |
+
placeholder="Enter your custom prompt for Gemini..."
|
70 |
+
/>
|
71 |
+
<div className="flex justify-end mt-2 space-x-2">
|
72 |
+
<span className="text-xs text-gray-500 self-center mr-auto">
|
73 |
+
Tip: Press {isMac ? 'β' : 'Ctrl'}+Enter to save
|
74 |
+
</span>
|
75 |
+
<button
|
76 |
+
onClick={() => setIsEditingPrompt(false)}
|
77 |
+
className="px-3 py-1 text-sm text-gray-600 border border-gray-300 rounded-md hover:bg-gray-100"
|
78 |
+
>
|
79 |
+
Cancel
|
80 |
+
</button>
|
81 |
+
<button
|
82 |
+
onClick={() => {
|
83 |
+
setCustomPrompt(tempPrompt);
|
84 |
+
setIsEditingPrompt(false);
|
85 |
+
}}
|
86 |
+
className="px-3 py-1 text-sm text-white bg-blue-600 rounded-md hover:bg-blue-700"
|
87 |
+
>
|
88 |
+
Save
|
89 |
+
</button>
|
90 |
+
</div>
|
91 |
+
</div>
|
92 |
+
) : (
|
93 |
+
<div className="p-3 bg-gray-50 border border-gray-200 rounded-lg text-sm text-gray-800 whitespace-pre-wrap">
|
94 |
+
{customPrompt}
|
95 |
+
</div>
|
96 |
+
)}
|
97 |
+
</>
|
98 |
)}
|
99 |
</div>
|
100 |
);
|
components/ThoughtBubble.js
CHANGED
@@ -48,10 +48,10 @@ const ThoughtBubble = ({
|
|
48 |
<span
|
49 |
style={{
|
50 |
fontSize: isMobile ? '28px' : '36px',
|
51 |
-
animation: 'thinking-
|
52 |
}}
|
53 |
>
|
54 |
-
|
55 |
</span>
|
56 |
);
|
57 |
}
|
|
|
48 |
<span
|
49 |
style={{
|
50 |
fontSize: isMobile ? '28px' : '36px',
|
51 |
+
animation: 'thinking-spin 1.5s linear infinite'
|
52 |
}}
|
53 |
>
|
54 |
+
π
|
55 |
</span>
|
56 |
);
|
57 |
}
|
components/UIUtils.js
CHANGED
@@ -52,6 +52,11 @@ export const AnimationStyles = () => {
|
|
52 |
50% { opacity: 0.7; transform: scale(0.95); }
|
53 |
}
|
54 |
|
|
|
|
|
|
|
|
|
|
|
55 |
@keyframes spring-out {
|
56 |
0% {
|
57 |
transform: scale(0) translateY(0);
|
|
|
52 |
50% { opacity: 0.7; transform: scale(0.95); }
|
53 |
}
|
54 |
|
55 |
+
@keyframes thinking-spin {
|
56 |
+
0% { transform: rotate(0deg); }
|
57 |
+
100% { transform: rotate(360deg); }
|
58 |
+
}
|
59 |
+
|
60 |
@keyframes spring-out {
|
61 |
0% {
|
62 |
transform: scale(0) translateY(0);
|
pages/_document.js
CHANGED
@@ -3,7 +3,12 @@ import { Html, Head, Main, NextScript } from "next/document";
|
|
3 |
export default function Document() {
|
4 |
return (
|
5 |
<Html lang="en">
|
6 |
-
<Head
|
|
|
|
|
|
|
|
|
|
|
7 |
<body className="antialiased">
|
8 |
<Main />
|
9 |
<NextScript />
|
|
|
3 |
export default function Document() {
|
4 |
return (
|
5 |
<Html lang="en">
|
6 |
+
<Head>
|
7 |
+
<link
|
8 |
+
rel="stylesheet"
|
9 |
+
href="https://fonts.googleapis.com/css2?family=Material+Symbols+Outlined:opsz,wght,FILL,GRAD@24,400,0,0&icon_names=cameraswitch"
|
10 |
+
/>
|
11 |
+
</Head>
|
12 |
<body className="antialiased">
|
13 |
<Main />
|
14 |
<NextScript />
|