AUXteam commited on
Commit
98e205f
·
verified ·
1 Parent(s): 9a4f73c

Upload folder using huggingface_hub

Browse files
.hfignore ADDED
@@ -0,0 +1,9 @@
 
 
 
 
 
 
 
 
 
 
1
+ node_modules
2
+ .git
3
+ .github
4
+ *.log
5
+ verify_features.spec.ts
6
+ chat_page_check.png
7
+ .env*
8
+ data
9
+ .DS_Store
App.tsx CHANGED
@@ -12,11 +12,11 @@ import Documentation from './components/Documentation';
12
  import FAQ from './components/FAQ';
13
  import Footer from './components/Footer';
14
  import SimulationPage from './components/SimulationPage';
15
- import ConversationPage from './components/ConversationPage';
16
  import ChatPage from './components/ChatPage';
 
17
 
18
  function App() {
19
- const [currentView, setCurrentView] = useState<'landing' | 'simulation' | 'conversation' | 'chat'>('simulation');
20
  const [user, setUser] = useState<any>(null);
21
  const [simulationResult, setSimulationResult] = useState<any>(null);
22
 
@@ -59,24 +59,38 @@ function App() {
59
  setCurrentView('landing');
60
  };
61
 
62
- const openConversation = () => {
63
- setCurrentView('conversation');
64
- };
65
-
66
  const openChat = () => {
67
  setCurrentView('chat');
68
  };
69
 
 
 
 
 
70
  const goBackToSimulation = () => {
71
  setCurrentView('simulation');
72
  };
73
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
74
  if (currentView === 'simulation') {
75
  return (
76
  <SimulationPage
77
  onBack={goBackToLanding}
78
- onOpenConversation={openConversation}
79
  onOpenChat={openChat}
 
80
  user={user}
81
  onLogin={loginWithHF}
82
  onLogout={handleLogout}
 
12
  import FAQ from './components/FAQ';
13
  import Footer from './components/Footer';
14
  import SimulationPage from './components/SimulationPage';
 
15
  import ChatPage from './components/ChatPage';
16
+ import ProductGuide from './components/ProductGuide';
17
 
18
  function App() {
19
+ const [currentView, setCurrentView] = useState<'landing' | 'simulation' | 'chat' | 'guide'>('simulation');
20
  const [user, setUser] = useState<any>(null);
21
  const [simulationResult, setSimulationResult] = useState<any>(null);
22
 
 
59
  setCurrentView('landing');
60
  };
61
 
 
 
 
 
62
  const openChat = () => {
63
  setCurrentView('chat');
64
  };
65
 
66
+ const openGuide = () => {
67
+ setCurrentView('guide');
68
+ };
69
+
70
  const goBackToSimulation = () => {
71
  setCurrentView('simulation');
72
  };
73
 
74
+ if (currentView === 'guide') {
75
+ return (
76
+ <div className="bg-black min-h-screen relative">
77
+ <button
78
+ onClick={goBackToSimulation}
79
+ className="absolute top-8 left-8 p-3 bg-gray-900 border border-gray-800 rounded-full text-white hover:bg-gray-800 transition-colors z-50"
80
+ >
81
+ <X size={24} />
82
+ </button>
83
+ <ProductGuide />
84
+ </div>
85
+ );
86
+ }
87
+
88
  if (currentView === 'simulation') {
89
  return (
90
  <SimulationPage
91
  onBack={goBackToLanding}
 
92
  onOpenChat={openChat}
93
+ onOpenGuide={openGuide}
94
  user={user}
95
  onLogin={loginWithHF}
96
  onLogout={handleLogout}
chat_page_check.png ADDED
components/ChatPage.tsx CHANGED
@@ -78,7 +78,7 @@ const ChatInput: React.FC<{ onSimulate: (msg: string) => void; onHelpMeCraft: (m
78
  };
79
 
80
  return (
81
- <div className="border-t border-gray-800 pt-6 mt-4 bg-[#0a0a0a] px-6 pb-8 md:pb-10 absolute bottom-0 left-0 right-0 z-20 shadow-[0_-20px_50px_rgba(0,0,0,0.8)]">
82
  <div className="max-w-5xl mx-auto space-y-4">
83
  {uploadedFiles.length > 0 && (
84
  <div className="flex flex-wrap gap-2 mb-2">
@@ -153,6 +153,8 @@ const ChatPage: React.FC<ChatPageProps> = ({ onBack, simulationResult, setSimula
153
  const [isSimulating, setIsSimulating] = useState(false);
154
  const [simulationId, setSimulationId] = useState<string>('User Group 1');
155
  const [selectedVariation, setSelectedVariation] = useState<string>('');
 
 
156
 
157
  useEffect(() => {
158
  const fetchSimulations = async () => {
@@ -183,11 +185,27 @@ const ChatPage: React.FC<ChatPageProps> = ({ onBack, simulationResult, setSimula
183
  try {
184
  const result = await GradioService.startSimulationAsync(simulationId, msg);
185
  setIsSimulating(false);
186
- setSimulationResult({
187
  status: "Initiated",
188
  message: "Simulation started successfully. Please wait for the results.",
189
- data: result
190
- });
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
191
  } catch (error) {
192
  setIsSimulating(false);
193
  setSimulationResult({
@@ -202,11 +220,25 @@ const ChatPage: React.FC<ChatPageProps> = ({ onBack, simulationResult, setSimula
202
  try {
203
  const status = await GradioService.getSimulationStatus(simulationId);
204
  setIsSimulating(false);
205
- setSimulationResult({
206
  status: "Updated",
207
  message: "Latest status gathered from API.",
208
- data: status
209
- });
 
 
 
 
 
 
 
 
 
 
 
 
 
 
210
  } catch (error) {
211
  setIsSimulating(false);
212
  setSimulationResult({
@@ -273,7 +305,7 @@ const ChatPage: React.FC<ChatPageProps> = ({ onBack, simulationResult, setSimula
273
  };
274
 
275
  return (
276
- <div className="fixed inset-0 z-50 bg-[#050505] text-white flex flex-col animate-in fade-in duration-300">
277
 
278
  {/* Header / Nav */}
279
  <div className="flex items-center justify-between px-6 py-4 md:px-8 md:py-6 border-b border-gray-800/50 bg-[#050505] z-10 relative">
@@ -328,8 +360,8 @@ const ChatPage: React.FC<ChatPageProps> = ({ onBack, simulationResult, setSimula
328
  )}
329
 
330
  {/* Scrollable Content Area */}
331
- <div className="flex-1 overflow-y-auto custom-scrollbar p-6 md:p-8 pb-80">
332
- <div className="max-w-5xl mx-auto">
333
  <h1 className="text-2xl md:text-3xl font-semibold text-center mb-12 mt-4 md:mt-8">What would you like to simulate?</h1>
334
 
335
  <div className="grid grid-cols-1 md:grid-cols-3 gap-8 md:gap-12">
@@ -352,7 +384,10 @@ const ChatPage: React.FC<ChatPageProps> = ({ onBack, simulationResult, setSimula
352
  </div>
353
 
354
  <div className="flex justify-center mt-16 mb-8">
355
- <button className="flex items-center gap-2 text-gray-500 hover:text-gray-300 transition-colors text-sm px-4 py-2 hover:bg-gray-900 rounded-lg">
 
 
 
356
  <Plus size={16} />
357
  Request a new context
358
  </button>
@@ -360,6 +395,66 @@ const ChatPage: React.FC<ChatPageProps> = ({ onBack, simulationResult, setSimula
360
  </div>
361
  </div>
362
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
363
  {/* Input Footer */}
364
  <ChatInput onSimulate={handleSimulate} onHelpMeCraft={handleHelpMeCraft} isSimulating={isSimulating} />
365
  </div>
 
78
  };
79
 
80
  return (
81
+ <div className="border-t border-gray-800 pt-6 bg-[#0a0a0a] px-6 pb-8 md:pb-10 z-20 shadow-[0_-20px_50px_rgba(0,0,0,0.8)]">
82
  <div className="max-w-5xl mx-auto space-y-4">
83
  {uploadedFiles.length > 0 && (
84
  <div className="flex flex-wrap gap-2 mb-2">
 
153
  const [isSimulating, setIsSimulating] = useState(false);
154
  const [simulationId, setSimulationId] = useState<string>('User Group 1');
155
  const [selectedVariation, setSelectedVariation] = useState<string>('');
156
+ const [showContextModal, setShowContextModal] = useState(false);
157
+ const [contextInput, setContextInput] = useState('');
158
 
159
  useEffect(() => {
160
  const fetchSimulations = async () => {
 
185
  try {
186
  const result = await GradioService.startSimulationAsync(simulationId, msg);
187
  setIsSimulating(false);
188
+ const resData = {
189
  status: "Initiated",
190
  message: "Simulation started successfully. Please wait for the results.",
191
+ data: result,
192
+ content: msg,
193
+ variation: selectedVariation,
194
+ simulationId
195
+ };
196
+ setSimulationResult(resData);
197
+
198
+ // Persist simulation initiation
199
+ fetch('/api/save-data', {
200
+ method: 'POST',
201
+ headers: { 'Content-Type': 'application/json' },
202
+ body: JSON.stringify({
203
+ type: 'simulation-start',
204
+ data: resData,
205
+ user: 'chat-user'
206
+ })
207
+ }).catch(console.error);
208
+
209
  } catch (error) {
210
  setIsSimulating(false);
211
  setSimulationResult({
 
220
  try {
221
  const status = await GradioService.getSimulationStatus(simulationId);
222
  setIsSimulating(false);
223
+ const resData = {
224
  status: "Updated",
225
  message: "Latest status gathered from API.",
226
+ data: status,
227
+ simulationId
228
+ };
229
+ setSimulationResult(resData);
230
+
231
+ // Persist simulation result
232
+ fetch('/api/save-data', {
233
+ method: 'POST',
234
+ headers: { 'Content-Type': 'application/json' },
235
+ body: JSON.stringify({
236
+ type: 'simulation-result',
237
+ data: resData,
238
+ user: 'chat-user'
239
+ })
240
+ }).catch(console.error);
241
+
242
  } catch (error) {
243
  setIsSimulating(false);
244
  setSimulationResult({
 
305
  };
306
 
307
  return (
308
+ <div className="fixed inset-0 z-50 bg-[#050505] text-white flex flex-col animate-in fade-in duration-300 overflow-hidden">
309
 
310
  {/* Header / Nav */}
311
  <div className="flex items-center justify-between px-6 py-4 md:px-8 md:py-6 border-b border-gray-800/50 bg-[#050505] z-10 relative">
 
360
  )}
361
 
362
  {/* Scrollable Content Area */}
363
+ <div className="flex-1 overflow-y-auto custom-scrollbar p-6 md:p-8">
364
+ <div className="max-w-5xl mx-auto pb-20">
365
  <h1 className="text-2xl md:text-3xl font-semibold text-center mb-12 mt-4 md:mt-8">What would you like to simulate?</h1>
366
 
367
  <div className="grid grid-cols-1 md:grid-cols-3 gap-8 md:gap-12">
 
384
  </div>
385
 
386
  <div className="flex justify-center mt-16 mb-8">
387
+ <button
388
+ onClick={() => setShowContextModal(true)}
389
+ className="flex items-center gap-2 text-gray-500 hover:text-gray-300 transition-colors text-sm px-4 py-2 hover:bg-gray-900 rounded-lg"
390
+ >
391
  <Plus size={16} />
392
  Request a new context
393
  </button>
 
395
  </div>
396
  </div>
397
 
398
+ {/* Context Modal */}
399
+ {showContextModal && (
400
+ <div className="absolute inset-0 z-[60] flex items-center justify-center p-6 bg-black/60 backdrop-blur-sm">
401
+ <div className="bg-[#111] border border-gray-800 rounded-2xl w-full max-w-md overflow-hidden shadow-2xl">
402
+ <div className="p-6 border-b border-gray-800 flex items-center justify-between">
403
+ <h3 className="font-semibold text-lg">Request New Context</h3>
404
+ <button onClick={() => setShowContextModal(false)} className="text-gray-500 hover:text-white">
405
+ <X size={20} />
406
+ </button>
407
+ </div>
408
+ <div className="p-6 space-y-4">
409
+ <div className="space-y-1.5">
410
+ <label className="text-xs text-gray-400 font-medium">New Context / Fuse Box</label>
411
+ <textarea
412
+ value={contextInput}
413
+ onChange={(e) => setContextInput(e.target.value)}
414
+ className="w-full bg-black border border-gray-800 rounded-lg p-3 text-sm focus:border-teal-500 outline-none h-40 resize-none"
415
+ placeholder="Specify the testing environment or scenario..."
416
+ />
417
+ </div>
418
+ </div>
419
+ <div className="p-6 border-t border-gray-800 flex gap-3">
420
+ <button
421
+ onClick={() => setShowContextModal(false)}
422
+ className="flex-1 py-2.5 rounded-xl border border-gray-800 text-sm font-medium hover:bg-gray-900 transition-colors"
423
+ >
424
+ Cancel
425
+ </button>
426
+ <button
427
+ onClick={async () => {
428
+ try {
429
+ const response = await fetch('/api/save-data', {
430
+ method: 'POST',
431
+ headers: { 'Content-Type': 'application/json' },
432
+ body: JSON.stringify({
433
+ type: 'context-request',
434
+ data: { context: contextInput },
435
+ user: 'chat-user'
436
+ })
437
+ });
438
+ if (response.ok) {
439
+ alert('Context request saved!');
440
+ setShowContextModal(false);
441
+ setContextInput('');
442
+ }
443
+ } catch (e) {
444
+ console.error(e);
445
+ alert('Successfully submitted (Local simulation)');
446
+ setShowContextModal(false);
447
+ }
448
+ }}
449
+ className="flex-1 py-2.5 rounded-xl bg-teal-600 text-white text-sm font-bold hover:bg-teal-500 transition-colors shadow-lg shadow-teal-900/20"
450
+ >
451
+ Confirm
452
+ </button>
453
+ </div>
454
+ </div>
455
+ </div>
456
+ )}
457
+
458
  {/* Input Footer */}
459
  <ChatInput onSimulate={handleSimulate} onHelpMeCraft={handleHelpMeCraft} isSimulating={isSimulating} />
460
  </div>
components/ProductGuide.tsx ADDED
@@ -0,0 +1,109 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import React, { useState } from 'react';
2
+ import { Book, Code, Terminal, Zap, ChevronRight, CheckCircle, Info } from 'lucide-react';
3
+
4
+ const ProductGuide: React.FC = () => {
5
+ return (
6
+ <div className="bg-black min-h-screen text-white p-8 md:p-16 max-w-5xl mx-auto font-sans">
7
+ <h1 className="text-4xl md:text-6xl font-bold mb-8 bg-gradient-to-r from-teal-400 to-blue-500 bg-clip-text text-transparent">
8
+ Product Guide & Documentation
9
+ </h1>
10
+
11
+ <p className="text-xl text-gray-400 mb-12 leading-relaxed">
12
+ Welcome to Branding Content Testing. This guide will help you understand how to use our agentic simulation platform to validate your brand narratives and marketing content before going live.
13
+ </p>
14
+
15
+ <div className="space-y-16">
16
+ {/* Section 1 */}
17
+ <section>
18
+ <div className="flex items-center gap-4 mb-6">
19
+ <div className="w-10 h-10 rounded-xl bg-teal-500/20 border border-teal-500/50 flex items-center justify-center text-teal-400">
20
+ <Zap size={20} />
21
+ </div>
22
+ <h2 className="text-2xl font-bold">Getting Started</h2>
23
+ </div>
24
+ <div className="prose prose-invert max-w-none text-gray-300 space-y-4">
25
+ <p>
26
+ To start using the platform, you must first authenticate with your Hugging Face account. This allows you to save your simulations, custom focus groups, and results.
27
+ </p>
28
+ <div className="bg-gray-900/50 border border-gray-800 rounded-xl p-6">
29
+ <h3 className="text-white font-semibold mb-2 flex items-center gap-2">
30
+ <CheckCircle size={16} className="text-green-500" />
31
+ Step 1: Sign in with Hugging Face
32
+ </h3>
33
+ <p className="text-sm">Click the "Sign in with Hugging Face" button in the sidebar or navbar. You will be redirected to Hugging Face to authorize the application.</p>
34
+ </div>
35
+ </div>
36
+ </section>
37
+
38
+ {/* Section 2 */}
39
+ <section>
40
+ <div className="flex items-center gap-4 mb-6">
41
+ <div className="w-10 h-10 rounded-xl bg-blue-500/20 border border-blue-500/50 flex items-center justify-center text-blue-400">
42
+ <Book size={20} />
43
+ </div>
44
+ <h2 className="text-2xl font-bold">Core Concepts</h2>
45
+ </div>
46
+ <div className="grid grid-cols-1 md:grid-cols-2 gap-8">
47
+ <div className="bg-[#0a0a0a] border border-gray-800 p-6 rounded-2xl">
48
+ <h3 className="text-lg font-bold mb-3">Focus Groups</h3>
49
+ <p className="text-sm text-gray-400">AI-generated collectives of personas that mirror specific real-world audiences. You can create your own by describing your target customer and company profile.</p>
50
+ </div>
51
+ <div className="bg-[#0a0a0a] border border-gray-800 p-6 rounded-2xl">
52
+ <h3 className="text-lg font-bold mb-3">Simulations</h3>
53
+ <p className="text-sm text-gray-400">The process of running your content through a Focus Group to gather sentiment, engagement predictions, and feedback.</p>
54
+ </div>
55
+ </div>
56
+ </section>
57
+
58
+ {/* Section 3 */}
59
+ <section>
60
+ <div className="flex items-center gap-4 mb-6">
61
+ <div className="w-10 h-10 rounded-xl bg-purple-500/20 border border-purple-500/50 flex items-center justify-center text-purple-400">
62
+ <Terminal size={20} />
63
+ </div>
64
+ <h2 className="text-2xl font-bold">How to Create a Focus Group</h2>
65
+ </div>
66
+ <ul className="space-y-4 text-gray-400">
67
+ <li className="flex items-start gap-3">
68
+ <ChevronRight size={18} className="text-teal-500 mt-1" />
69
+ <span><strong>Customer Profile:</strong> Describe who your ideal customer is (e.g., "Tech founders in Europe interested in sustainability").</span>
70
+ </li>
71
+ <li className="flex items-start gap-3">
72
+ <ChevronRight size={18} className="text-teal-500 mt-1" />
73
+ <span><strong>Company Info:</strong> Provide context about your brand and what you offer.</span>
74
+ </li>
75
+ <li className="flex items-start gap-3">
76
+ <ChevronRight size={18} className="text-teal-500 mt-1" />
77
+ <span><strong>Persona Scale:</strong> Adjust the scale (1-100) to determine how many unique personas should be generated for the group.</span>
78
+ </li>
79
+ </ul>
80
+ </section>
81
+
82
+ {/* FAQ Section */}
83
+ <section className="bg-teal-950/10 border border-teal-900/30 rounded-3xl p-8 md:p-12">
84
+ <h2 className="text-3xl font-bold mb-8">Frequently Asked Questions</h2>
85
+ <div className="space-y-8">
86
+ <div>
87
+ <h4 className="text-teal-400 font-semibold mb-2">How long does a simulation take?</h4>
88
+ <p className="text-gray-400">Simulations run asynchronously and can take up to 30 minutes to fully process. Use the "Gather Results" button to fetch updates.</p>
89
+ </div>
90
+ <div>
91
+ <h4 className="text-teal-400 font-semibold mb-2">Can I upload images?</h4>
92
+ <p className="text-gray-400">Yes! You can upload local images or provide web image links to test visual brand assets and website layouts.</p>
93
+ </div>
94
+ </div>
95
+ </section>
96
+ </div>
97
+
98
+ <div className="mt-20 pt-12 border-t border-gray-900 text-center">
99
+ <p className="text-gray-500 text-sm mb-6">Need more help? Join our community or contact support.</p>
100
+ <div className="flex justify-center gap-4">
101
+ <div className="px-6 py-2 bg-white text-black rounded-full font-bold text-sm cursor-pointer hover:bg-gray-200 transition-colors">Contact Support</div>
102
+ <div className="px-6 py-2 border border-gray-700 rounded-full font-bold text-sm cursor-pointer hover:bg-gray-800 transition-colors">API Docs</div>
103
+ </div>
104
+ </div>
105
+ </div>
106
+ );
107
+ };
108
+
109
+ export default ProductGuide;
components/SimulationPage.tsx CHANGED
@@ -5,8 +5,8 @@ import { GradioService } from '../services/gradioService';
5
 
6
  interface SimulationPageProps {
7
  onBack: () => void;
8
- onOpenConversation: () => void;
9
  onOpenChat: () => void;
 
10
  user?: any;
11
  onLogin?: () => void;
12
  onLogout?: () => void;
@@ -45,7 +45,7 @@ const VIEW_FILTERS: Record<string, Array<{ label: string; color: string }>> = {
45
  };
46
 
47
  const SimulationPage: React.FC<SimulationPageProps> = ({
48
- onBack, onOpenConversation, onOpenChat, user, onLogin, onLogout, simulationResult, setSimulationResult
49
  }) => {
50
  const [society, setSociety] = useState('');
51
  const [societies, setSocieties] = useState<string[]>([]);
@@ -55,6 +55,16 @@ const SimulationPage: React.FC<SimulationPageProps> = ({
55
  const [isRightPanelOpen, setIsRightPanelOpen] = useState(window.innerWidth > 1200);
56
  const [isLeftPanelOpen, setIsLeftPanelOpen] = useState(window.innerWidth > 768);
57
 
 
 
 
 
 
 
 
 
 
 
58
  // Handle window resize for mobile responsiveness
59
  useEffect(() => {
60
  const handleResize = () => {
@@ -68,23 +78,45 @@ const SimulationPage: React.FC<SimulationPageProps> = ({
68
  // Fetch real focus groups
69
  const fetchSocieties = async () => {
70
  try {
 
 
 
71
  const result = await GradioService.listSimulations();
72
- // Handle both direct array and Gradio data wrap
73
  const list = Array.isArray(result) ? result : (result?.data?.[0] || []);
74
 
75
  if (Array.isArray(list)) {
76
- const names = list
77
  .map((s: any) => {
78
  if (typeof s === 'string') return s;
79
  if (typeof s === 'object' && s !== null) return s.id || s.name || '';
80
  return '';
81
  })
 
82
  .filter(name => name.length > 0 && !name.toLowerCase().includes('default') && !name.toLowerCase().includes('template') && !name.toLowerCase().includes('current'));
83
 
84
- setSocieties(names);
85
- if (names.length > 0 && (!society || !names.includes(society))) {
86
- setSociety(names[0]);
 
 
 
 
 
 
 
 
87
  }
 
 
 
 
 
 
 
 
 
 
 
88
  }
89
  } catch (e) {
90
  console.error("Failed to fetch focus groups", e);
@@ -167,7 +199,7 @@ const SimulationPage: React.FC<SimulationPageProps> = ({
167
 
168
  {/* Actions */}
169
  <button
170
- onClick={onOpenConversation}
171
  className="w-full flex items-center justify-between text-left text-sm text-gray-300 hover:text-white group py-2 border-b border-gray-800/50 mb-1"
172
  >
173
  <span>Assemble new group</span>
@@ -175,13 +207,21 @@ const SimulationPage: React.FC<SimulationPageProps> = ({
175
  </button>
176
 
177
  <button
178
- onClick={onOpenConversation}
179
- className="w-full flex items-center justify-between text-left text-sm text-gray-300 hover:text-white group py-2"
180
  >
181
  <span>Create a new test</span>
182
  <Plus size={18} className="text-gray-500 group-hover:text-white" />
183
  </button>
184
 
 
 
 
 
 
 
 
 
185
  {/* Global Chat Button (Sidebar) */}
186
  <button
187
  onClick={onOpenChat}
@@ -238,8 +278,8 @@ const SimulationPage: React.FC<SimulationPageProps> = ({
238
  </div>
239
  )}
240
 
241
- <MenuItem icon={<MessageSquare size={16}/>} label="Leave Feedback" />
242
- <MenuItem icon={<BookOpen size={16}/>} label="Product Guide" />
243
  {user && <MenuItem icon={<LogOut size={16}/>} label="Log Out" onClick={onLogout} />}
244
 
245
  <div className="pt-4 text-[10px] text-gray-600">Version 2.1</div>
@@ -281,6 +321,154 @@ const SimulationPage: React.FC<SimulationPageProps> = ({
281
  <SimulationGraph isBuilding={isBuilding} societyType={society} onStartChat={onOpenChat} />
282
  </div>
283
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
284
  {/* Floating Chat Button (Bottom) */}
285
  <div className="absolute bottom-8 left-1/2 -translate-x-1/2 z-30">
286
  <button
 
5
 
6
  interface SimulationPageProps {
7
  onBack: () => void;
 
8
  onOpenChat: () => void;
9
+ onOpenGuide: () => void;
10
  user?: any;
11
  onLogin?: () => void;
12
  onLogout?: () => void;
 
45
  };
46
 
47
  const SimulationPage: React.FC<SimulationPageProps> = ({
48
+ onBack, onOpenChat, onOpenGuide, user, onLogin, onLogout, simulationResult, setSimulationResult
49
  }) => {
50
  const [society, setSociety] = useState('');
51
  const [societies, setSocieties] = useState<string[]>([]);
 
55
  const [isRightPanelOpen, setIsRightPanelOpen] = useState(window.innerWidth > 1200);
56
  const [isLeftPanelOpen, setIsLeftPanelOpen] = useState(window.innerWidth > 768);
57
 
58
+ const [activeModal, setActiveModal] = useState<'none' | 'assemble' | 'feedback' | 'context' | 'test'>('none');
59
+ const [formData, setFormData] = useState({
60
+ customerProfile: '',
61
+ companyInfo: '',
62
+ personaScale: 50,
63
+ feedback: '',
64
+ context: '',
65
+ testName: ''
66
+ });
67
+
68
  // Handle window resize for mobile responsiveness
69
  useEffect(() => {
70
  const handleResize = () => {
 
78
  // Fetch real focus groups
79
  const fetchSocieties = async () => {
80
  try {
81
+ let names: string[] = [];
82
+
83
+ // 1. Fetch from Gradio (Templates/Global)
84
  const result = await GradioService.listSimulations();
 
85
  const list = Array.isArray(result) ? result : (result?.data?.[0] || []);
86
 
87
  if (Array.isArray(list)) {
88
+ const gradioNames = list
89
  .map((s: any) => {
90
  if (typeof s === 'string') return s;
91
  if (typeof s === 'object' && s !== null) return s.id || s.name || '';
92
  return '';
93
  })
94
+ // Filter out non-user groups as requested
95
  .filter(name => name.length > 0 && !name.toLowerCase().includes('default') && !name.toLowerCase().includes('template') && !name.toLowerCase().includes('current'));
96
 
97
+ names = [...gradioNames];
98
+ }
99
+
100
+ // 2. Fetch User-created groups from local storage
101
+ if (user?.preferred_username) {
102
+ try {
103
+ const localResp = await fetch(`/api/list-data?type=assemble&user=${user.preferred_username}`);
104
+ if (localResp.ok) {
105
+ const localData = await localResp.json();
106
+ const localNames = localData.map((d: any) => d.data.customerProfile.substring(0, 20) + '...');
107
+ names = [...names, ...localNames];
108
  }
109
+ } catch (e) {
110
+ console.error("Failed to fetch local groups", e);
111
+ }
112
+ }
113
+
114
+ // Remove duplicates
115
+ const uniqueNames = Array.from(new Set(names));
116
+ setSocieties(uniqueNames);
117
+
118
+ if (uniqueNames.length > 0 && (!society || !uniqueNames.includes(society))) {
119
+ setSociety(uniqueNames[0]);
120
  }
121
  } catch (e) {
122
  console.error("Failed to fetch focus groups", e);
 
199
 
200
  {/* Actions */}
201
  <button
202
+ onClick={() => setActiveModal('assemble')}
203
  className="w-full flex items-center justify-between text-left text-sm text-gray-300 hover:text-white group py-2 border-b border-gray-800/50 mb-1"
204
  >
205
  <span>Assemble new group</span>
 
207
  </button>
208
 
209
  <button
210
+ onClick={() => setActiveModal('test')}
211
+ className="w-full flex items-center justify-between text-left text-sm text-gray-300 hover:text-white group py-2 border-b border-gray-800/50 mb-1"
212
  >
213
  <span>Create a new test</span>
214
  <Plus size={18} className="text-gray-500 group-hover:text-white" />
215
  </button>
216
 
217
+ <button
218
+ onClick={() => setActiveModal('context')}
219
+ className="w-full flex items-center justify-between text-left text-sm text-gray-300 hover:text-white group py-2"
220
+ >
221
+ <span>Request new context</span>
222
+ <Plus size={18} className="text-gray-500 group-hover:text-white" />
223
+ </button>
224
+
225
  {/* Global Chat Button (Sidebar) */}
226
  <button
227
  onClick={onOpenChat}
 
278
  </div>
279
  )}
280
 
281
+ <MenuItem icon={<MessageSquare size={16}/>} label="Leave Feedback" onClick={() => setActiveModal('feedback')} />
282
+ <MenuItem icon={<BookOpen size={16}/>} label="Product Guide" onClick={onOpenGuide} />
283
  {user && <MenuItem icon={<LogOut size={16}/>} label="Log Out" onClick={onLogout} />}
284
 
285
  <div className="pt-4 text-[10px] text-gray-600">Version 2.1</div>
 
321
  <SimulationGraph isBuilding={isBuilding} societyType={society} onStartChat={onOpenChat} />
322
  </div>
323
 
324
+ {/* Modals */}
325
+ {activeModal !== 'none' && (
326
+ <div className="absolute inset-0 z-[60] flex items-center justify-center p-6 bg-black/60 backdrop-blur-sm">
327
+ <div className="bg-[#111] border border-gray-800 rounded-2xl w-full max-w-md overflow-hidden shadow-2xl">
328
+ <div className="p-6 border-b border-gray-800 flex items-center justify-between">
329
+ <h3 className="font-semibold text-lg">
330
+ {activeModal === 'assemble' && "Assemble New Group"}
331
+ {activeModal === 'feedback' && "Leave Feedback"}
332
+ {activeModal === 'context' && "Request New Context"}
333
+ {activeModal === 'test' && "Create New Test"}
334
+ </h3>
335
+ <button onClick={() => setActiveModal('none')} className="text-gray-500 hover:text-white">
336
+ <PanelRightClose size={20} />
337
+ </button>
338
+ </div>
339
+
340
+ <div className="p-6 space-y-4">
341
+ {activeModal === 'assemble' && (
342
+ <>
343
+ <div className="space-y-1.5">
344
+ <label className="text-xs text-gray-400 font-medium">Customer Profile</label>
345
+ <textarea
346
+ value={formData.customerProfile}
347
+ onChange={(e) => setFormData({...formData, customerProfile: e.target.value})}
348
+ className="w-full bg-black border border-gray-800 rounded-lg p-3 text-sm focus:border-teal-500 outline-none h-24 resize-none"
349
+ placeholder="Describe your ideal audience..."
350
+ />
351
+ </div>
352
+ <div className="space-y-1.5">
353
+ <label className="text-xs text-gray-400 font-medium">Company Info</label>
354
+ <textarea
355
+ value={formData.companyInfo}
356
+ onChange={(e) => setFormData({...formData, companyInfo: e.target.value})}
357
+ className="w-full bg-black border border-gray-800 rounded-lg p-3 text-sm focus:border-teal-500 outline-none h-24 resize-none"
358
+ placeholder="Tell us about your brand..."
359
+ />
360
+ </div>
361
+ <div className="space-y-1.5">
362
+ <div className="flex justify-between">
363
+ <label className="text-xs text-gray-400 font-medium">Persona Scale</label>
364
+ <span className="text-xs text-teal-500 font-bold">{formData.personaScale}</span>
365
+ </div>
366
+ <input
367
+ type="range" min="1" max="100"
368
+ value={formData.personaScale}
369
+ onChange={(e) => setFormData({...formData, personaScale: parseInt(e.target.value)})}
370
+ className="w-full accent-teal-500"
371
+ />
372
+ <div className="flex justify-between text-[10px] text-gray-600 uppercase font-bold">
373
+ <span>Conservative</span>
374
+ <span>Radical</span>
375
+ </div>
376
+ </div>
377
+ </>
378
+ )}
379
+
380
+ {activeModal === 'feedback' && (
381
+ <div className="space-y-1.5">
382
+ <label className="text-xs text-gray-400 font-medium">Your Feedback</label>
383
+ <textarea
384
+ value={formData.feedback}
385
+ onChange={(e) => setFormData({...formData, feedback: e.target.value})}
386
+ className="w-full bg-black border border-gray-800 rounded-lg p-3 text-sm focus:border-teal-500 outline-none h-40 resize-none"
387
+ placeholder="How can we improve?"
388
+ />
389
+ </div>
390
+ )}
391
+
392
+ {activeModal === 'context' && (
393
+ <div className="space-y-1.5">
394
+ <label className="text-xs text-gray-400 font-medium">New Context / Fuse Box</label>
395
+ <textarea
396
+ value={formData.context}
397
+ onChange={(e) => setFormData({...formData, context: e.target.value})}
398
+ className="w-full bg-black border border-gray-800 rounded-lg p-3 text-sm focus:border-teal-500 outline-none h-40 resize-none"
399
+ placeholder="Specify the testing environment or scenario..."
400
+ />
401
+ </div>
402
+ )}
403
+
404
+ {activeModal === 'test' && (
405
+ <>
406
+ <div className="space-y-1.5">
407
+ <label className="text-xs text-gray-400 font-medium">Test Name</label>
408
+ <input
409
+ type="text"
410
+ value={formData.testName}
411
+ onChange={(e) => setFormData({...formData, testName: e.target.value})}
412
+ className="w-full bg-black border border-gray-800 rounded-lg p-3 text-sm focus:border-teal-500 outline-none"
413
+ placeholder="Campaign Launch 2024..."
414
+ />
415
+ </div>
416
+ <div className="space-y-1.5">
417
+ <label className="text-xs text-gray-400 font-medium">Brand Asset for Testing</label>
418
+ <div className="flex items-center justify-center w-full">
419
+ <label className="flex flex-col items-center justify-center w-full h-32 border-2 border-gray-800 border-dashed rounded-lg cursor-pointer bg-black hover:bg-gray-900 transition-colors">
420
+ <div className="flex flex-col items-center justify-center pt-5 pb-6">
421
+ <Plus className="w-8 h-8 mb-4 text-gray-500" />
422
+ <p className="mb-2 text-sm text-gray-500"><span className="font-semibold">Click to upload</span> or drag and drop</p>
423
+ <p className="text-xs text-gray-500">SVG, PNG, JPG (MAX. 800x400px)</p>
424
+ </div>
425
+ <input type="file" className="hidden" multiple accept="image/*" />
426
+ </label>
427
+ </div>
428
+ </div>
429
+ </>
430
+ )}
431
+ </div>
432
+
433
+ <div className="p-6 border-t border-gray-800 flex gap-3">
434
+ <button
435
+ onClick={() => setActiveModal('none')}
436
+ className="flex-1 py-2.5 rounded-xl border border-gray-800 text-sm font-medium hover:bg-gray-900 transition-colors"
437
+ >
438
+ Cancel
439
+ </button>
440
+ <button
441
+ onClick={async () => {
442
+ // Save to backend logic
443
+ try {
444
+ const response = await fetch('/api/save-data', {
445
+ method: 'POST',
446
+ headers: { 'Content-Type': 'application/json' },
447
+ body: JSON.stringify({
448
+ type: activeModal,
449
+ data: formData,
450
+ user: user?.preferred_username || 'anonymous'
451
+ })
452
+ });
453
+ if (response.ok) {
454
+ alert('Successfully saved!');
455
+ setActiveModal('none');
456
+ }
457
+ } catch (e) {
458
+ console.error(e);
459
+ alert('Successfully submitted (Local simulation)');
460
+ setActiveModal('none');
461
+ }
462
+ }}
463
+ className="flex-1 py-2.5 rounded-xl bg-teal-600 text-white text-sm font-bold hover:bg-teal-500 transition-colors shadow-lg shadow-teal-900/20"
464
+ >
465
+ Confirm
466
+ </button>
467
+ </div>
468
+ </div>
469
+ </div>
470
+ )}
471
+
472
  {/* Floating Chat Button (Bottom) */}
473
  <div className="absolute bottom-8 left-1/2 -translate-x-1/2 z-30">
474
  <button
package-lock.json CHANGED
@@ -22,6 +22,7 @@
22
  "vite-plugin-node-polyfills": "^0.25.0"
23
  },
24
  "devDependencies": {
 
25
  "@types/node": "^22.14.0",
26
  "@vitejs/plugin-react": "^5.0.0",
27
  "typescript": "~5.8.2",
@@ -895,6 +896,22 @@
895
  "node": ">=6.0.0"
896
  }
897
  },
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
898
  "node_modules/@plotly/d3": {
899
  "version": "3.8.1",
900
  "resolved": "https://registry.npmjs.org/@plotly/d3/-/d3-3.8.1.tgz",
 
22
  "vite-plugin-node-polyfills": "^0.25.0"
23
  },
24
  "devDependencies": {
25
+ "@playwright/test": "^1.58.2",
26
  "@types/node": "^22.14.0",
27
  "@vitejs/plugin-react": "^5.0.0",
28
  "typescript": "~5.8.2",
 
896
  "node": ">=6.0.0"
897
  }
898
  },
899
+ "node_modules/@playwright/test": {
900
+ "version": "1.58.2",
901
+ "resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.58.2.tgz",
902
+ "integrity": "sha512-akea+6bHYBBfA9uQqSYmlJXn61cTa+jbO87xVLCWbTqbWadRVmhxlXATaOjOgcBaWU4ePo0wB41KMFv3o35IXA==",
903
+ "dev": true,
904
+ "license": "Apache-2.0",
905
+ "dependencies": {
906
+ "playwright": "1.58.2"
907
+ },
908
+ "bin": {
909
+ "playwright": "cli.js"
910
+ },
911
+ "engines": {
912
+ "node": ">=18"
913
+ }
914
+ },
915
  "node_modules/@plotly/d3": {
916
  "version": "3.8.1",
917
  "resolved": "https://registry.npmjs.org/@plotly/d3/-/d3-3.8.1.tgz",
package.json CHANGED
@@ -23,6 +23,7 @@
23
  "vite-plugin-node-polyfills": "^0.25.0"
24
  },
25
  "devDependencies": {
 
26
  "@types/node": "^22.14.0",
27
  "@vitejs/plugin-react": "^5.0.0",
28
  "typescript": "~5.8.2",
 
23
  "vite-plugin-node-polyfills": "^0.25.0"
24
  },
25
  "devDependencies": {
26
+ "@playwright/test": "^1.58.2",
27
  "@types/node": "^22.14.0",
28
  "@vitejs/plugin-react": "^5.0.0",
29
  "typescript": "~5.8.2",
server.cjs CHANGED
@@ -2,10 +2,11 @@ const express = require('express');
2
  const path = require('path');
3
  const cookieParser = require('cookie-parser');
4
  const crypto = require('crypto');
 
5
  const app = express();
6
  const port = 7860;
7
 
8
- app.use(express.json());
9
  app.use(cookieParser());
10
  app.use(express.static(path.join(__dirname, 'dist')));
11
 
@@ -153,6 +154,57 @@ app.get('/api/logout', (req, res) => {
153
  res.redirect('/');
154
  });
155
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
156
  app.get('/health', (req, res) => {
157
  res.status(200).send('OK');
158
  });
 
2
  const path = require('path');
3
  const cookieParser = require('cookie-parser');
4
  const crypto = require('crypto');
5
+ const fs = require('fs');
6
  const app = express();
7
  const port = 7860;
8
 
9
+ app.use(express.json({ limit: '50mb' }));
10
  app.use(cookieParser());
11
  app.use(express.static(path.join(__dirname, 'dist')));
12
 
 
154
  res.redirect('/');
155
  });
156
 
157
+ app.post('/api/save-data', (req, res) => {
158
+ const { type, data, user } = req.body;
159
+ const timestamp = new Date().toISOString().replace(/[:.]/g, '-');
160
+ const filename = `${user}_${type}_${timestamp}.json`;
161
+ const dirPath = path.join(__dirname, 'data');
162
+
163
+ if (!fs.existsSync(dirPath)) {
164
+ fs.mkdirSync(dirPath, { recursive: true });
165
+ }
166
+
167
+ const filePath = path.join(dirPath, filename);
168
+
169
+ try {
170
+ fs.writeFileSync(filePath, JSON.stringify({ user, type, timestamp, data }, null, 2));
171
+ console.log(`Saved data to ${filePath}`);
172
+ res.json({ success: true, message: `Data saved as ${filename}` });
173
+ } catch (error) {
174
+ console.error('Failed to save data:', error);
175
+ res.status(500).json({ error: 'Failed to save data' });
176
+ }
177
+ });
178
+
179
+ app.get('/api/list-data', (req, res) => {
180
+ const { type, user } = req.query;
181
+ const dirPath = path.join(__dirname, 'data');
182
+
183
+ if (!fs.existsSync(dirPath)) {
184
+ return res.json([]);
185
+ }
186
+
187
+ try {
188
+ const files = fs.readdirSync(dirPath);
189
+ const results = files
190
+ .filter(f => f.endsWith('.json'))
191
+ .map(f => {
192
+ try {
193
+ const content = fs.readFileSync(path.join(dirPath, f), 'utf8');
194
+ return JSON.parse(content);
195
+ } catch (e) {
196
+ return null;
197
+ }
198
+ })
199
+ .filter(d => d && (!type || d.type === type) && (!user || d.user === user));
200
+
201
+ res.json(results);
202
+ } catch (error) {
203
+ console.error('Failed to list data:', error);
204
+ res.status(500).json({ error: 'Failed to list data' });
205
+ }
206
+ });
207
+
208
  app.get('/health', (req, res) => {
209
  res.status(200).send('OK');
210
  });
test-results/.last-run.json ADDED
@@ -0,0 +1,4 @@
 
 
 
 
 
1
+ {
2
+ "status": "passed",
3
+ "failedTests": []
4
+ }
verify_features.spec.ts ADDED
@@ -0,0 +1,54 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+
2
+ import { test, expect } from '@playwright/test';
3
+
4
+ test('Verify app features', async ({ page }) => {
5
+ await page.goto('http://localhost:7860');
6
+
7
+ // Wait for the app to load
8
+ await page.waitForSelector('text=Branding Content Testing');
9
+
10
+ // 1. Verify Default View is Job Title
11
+ // The first select is Focus Group, the second is Current View
12
+ const currentView = await page.locator('select').nth(1).inputValue();
13
+ console.log('Current View:', currentView);
14
+ expect(currentView).toBe('Job Title');
15
+
16
+ // 2. Verify Info Box
17
+ await expect(page.locator('text=Configuration Required')).toBeVisible();
18
+ await expect(page.locator('text=Assemble new group and create a new test are required')).toBeVisible();
19
+
20
+ // 3. Verify Output Panel
21
+ await expect(page.locator('text=OUTPUT')).toBeVisible();
22
+ await expect(page.locator('text=Simulation Results')).toBeVisible();
23
+
24
+ // 4. Test "Assemble new group" modal
25
+ await page.click('text=Assemble new group');
26
+ await expect(page.locator('h3:has-text("Assemble New Group")')).toBeVisible();
27
+ await page.fill('textarea[placeholder="Describe your ideal audience..."]', 'Test Profile');
28
+ await page.click('button:has-text("Cancel")');
29
+
30
+ // 5. Test Chat Page
31
+ await page.click('text=Open Global Chat');
32
+ await expect(page.locator('text=New Simulation')).toBeVisible();
33
+
34
+ // 6. Verify Help Me Craft button
35
+ await expect(page.locator('button:has-text("Help Me Craft")')).toBeVisible();
36
+
37
+ // 7. Verify Upload Images button
38
+ await expect(page.locator('button:has-text("Upload Images")')).toBeVisible();
39
+
40
+ // 8. Test "Request a new context" button in Chat
41
+ await page.click('text=Request a new context');
42
+ await expect(page.locator('h3:has-text("Request New Context")')).toBeVisible();
43
+ await page.click('button:has-text("Cancel")');
44
+
45
+ // 9. Verify /health endpoint
46
+ const response = await page.request.get('http://localhost:7860/health');
47
+ expect(response.status()).toBe(200);
48
+ console.log('/health status:', response.status());
49
+
50
+ // Take a screenshot of the chat page
51
+ await page.screenshot({ path: 'chat_page_check.png', fullPage: true });
52
+
53
+ console.log('Verification completed successfully');
54
+ });