Spaces:
Running
Running
Upload folder using huggingface_hub
Browse files- .github/workflows/sync-to-hf.yml +19 -0
- .gitignore +24 -0
- App.tsx +38 -2
- components/ChatPage.tsx +107 -30
- components/SimulationPage.tsx +56 -6
- inspect_gradio.cjs +17 -0
- inspect_gradio.ts +9 -0
- server.cjs +7 -0
- services/gradioService.ts +29 -7
.github/workflows/sync-to-hf.yml
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
name: Sync to Hugging Face hub
|
| 2 |
+
on:
|
| 3 |
+
push:
|
| 4 |
+
branches: [main]
|
| 5 |
+
workflow_dispatch:
|
| 6 |
+
|
| 7 |
+
jobs:
|
| 8 |
+
sync-to-hub:
|
| 9 |
+
runs-on: ubuntu-latest
|
| 10 |
+
steps:
|
| 11 |
+
- uses: actions/checkout@v3
|
| 12 |
+
with:
|
| 13 |
+
fetch-depth: 0
|
| 14 |
+
lfs: true
|
| 15 |
+
- name: Push to hub
|
| 16 |
+
env:
|
| 17 |
+
HF_TOKEN: ${{ secrets.HF_TOKEN }}
|
| 18 |
+
run: |
|
| 19 |
+
git push --force https://AUXteam:$HF_TOKEN@huggingface.co/spaces/AUXteam/UserSyncInterface main
|
.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
CHANGED
|
@@ -19,8 +19,21 @@ import ChatPage from './components/ChatPage';
|
|
| 19 |
function App() {
|
| 20 |
const [currentView, setCurrentView] = useState<'landing' | 'simulation' | 'conversation' | 'chat'>('simulation');
|
| 21 |
const [user, setUser] = useState<any>(null);
|
|
|
|
|
|
|
| 22 |
|
| 23 |
useEffect(() => {
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 24 |
const handleAuth = async () => {
|
| 25 |
try {
|
| 26 |
const oauthResult = await oauthHandleRedirectIfPresent();
|
|
@@ -36,7 +49,22 @@ function App() {
|
|
| 36 |
}, []);
|
| 37 |
|
| 38 |
const loginWithHF = async () => {
|
| 39 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 40 |
};
|
| 41 |
|
| 42 |
const startSimulation = () => {
|
|
@@ -68,6 +96,8 @@ function App() {
|
|
| 68 |
onOpenChat={openChat}
|
| 69 |
user={user}
|
| 70 |
onLogin={loginWithHF}
|
|
|
|
|
|
|
| 71 |
/>
|
| 72 |
);
|
| 73 |
}
|
|
@@ -77,7 +107,13 @@ function App() {
|
|
| 77 |
}
|
| 78 |
|
| 79 |
if (currentView === 'chat') {
|
| 80 |
-
return
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 81 |
}
|
| 82 |
|
| 83 |
return (
|
|
|
|
| 19 |
function App() {
|
| 20 |
const [currentView, setCurrentView] = useState<'landing' | 'simulation' | 'conversation' | 'chat'>('simulation');
|
| 21 |
const [user, setUser] = useState<any>(null);
|
| 22 |
+
const [config, setConfig] = useState<{ clientId?: string; scopes?: string }>({});
|
| 23 |
+
const [simulationResult, setSimulationResult] = useState<any>(null);
|
| 24 |
|
| 25 |
useEffect(() => {
|
| 26 |
+
const fetchConfig = async () => {
|
| 27 |
+
try {
|
| 28 |
+
const response = await fetch('/api/config');
|
| 29 |
+
const data = await response.json();
|
| 30 |
+
setConfig(data);
|
| 31 |
+
} catch (error) {
|
| 32 |
+
console.error("Failed to fetch config:", error);
|
| 33 |
+
}
|
| 34 |
+
};
|
| 35 |
+
fetchConfig();
|
| 36 |
+
|
| 37 |
const handleAuth = async () => {
|
| 38 |
try {
|
| 39 |
const oauthResult = await oauthHandleRedirectIfPresent();
|
|
|
|
| 49 |
}, []);
|
| 50 |
|
| 51 |
const loginWithHF = async () => {
|
| 52 |
+
try {
|
| 53 |
+
const url = await oauthLoginUrl({
|
| 54 |
+
clientId: config.clientId,
|
| 55 |
+
scopes: config.scopes,
|
| 56 |
+
redirectUrl: window.location.origin + "/"
|
| 57 |
+
});
|
| 58 |
+
// Use window.top for redirecting out of the Space iframe
|
| 59 |
+
if (window.top) {
|
| 60 |
+
window.top.location.href = url;
|
| 61 |
+
} else {
|
| 62 |
+
window.location.href = url;
|
| 63 |
+
}
|
| 64 |
+
} catch (error) {
|
| 65 |
+
console.error("Login error:", error);
|
| 66 |
+
alert("Failed to initiate login. See console for details.");
|
| 67 |
+
}
|
| 68 |
};
|
| 69 |
|
| 70 |
const startSimulation = () => {
|
|
|
|
| 96 |
onOpenChat={openChat}
|
| 97 |
user={user}
|
| 98 |
onLogin={loginWithHF}
|
| 99 |
+
simulationResult={simulationResult}
|
| 100 |
+
setSimulationResult={setSimulationResult}
|
| 101 |
/>
|
| 102 |
);
|
| 103 |
}
|
|
|
|
| 107 |
}
|
| 108 |
|
| 109 |
if (currentView === 'chat') {
|
| 110 |
+
return (
|
| 111 |
+
<ChatPage
|
| 112 |
+
onBack={goBackToSimulation}
|
| 113 |
+
simulationResult={simulationResult}
|
| 114 |
+
setSimulationResult={setSimulationResult}
|
| 115 |
+
/>
|
| 116 |
+
);
|
| 117 |
}
|
| 118 |
|
| 119 |
return (
|
components/ChatPage.tsx
CHANGED
|
@@ -1,11 +1,13 @@
|
|
| 1 |
|
| 2 |
-
import React, { useState, useRef } from 'react';
|
| 3 |
-
import { X, ClipboardList, Linkedin, Instagram, Mail, Layout, Edit3, MonitorPlay, Lightbulb, Image, Plus, Sparkles, Zap, AlertCircle, Video, Megaphone, Link as LinkIcon, Loader2, RefreshCw } from 'lucide-react';
|
| 4 |
import { GradioService } from '../services/gradioService';
|
| 5 |
|
| 6 |
// --- Types ---
|
| 7 |
interface ChatPageProps {
|
| 8 |
onBack: () => void;
|
|
|
|
|
|
|
| 9 |
}
|
| 10 |
|
| 11 |
// --- Sub-components (Modular Structure) ---
|
|
@@ -43,6 +45,7 @@ const CategoryCard: React.FC<{ title: string; options: { label: string; icon: Re
|
|
| 43 |
|
| 44 |
const ChatInput: React.FC<{ onSimulate: (msg: string) => void; onHelpMeCraft: (msg: string) => void; isSimulating: boolean }> = ({ onSimulate, onHelpMeCraft, isSimulating }) => {
|
| 45 |
const [message, setMessage] = useState('');
|
|
|
|
| 46 |
const fileInputRef = useRef<HTMLInputElement>(null);
|
| 47 |
|
| 48 |
const handleUploadClick = () => {
|
|
@@ -50,16 +53,34 @@ const ChatInput: React.FC<{ onSimulate: (msg: string) => void; onHelpMeCraft: (m
|
|
| 50 |
};
|
| 51 |
|
| 52 |
const handleFileChange = (e: React.ChangeEvent<HTMLInputElement>) => {
|
| 53 |
-
const
|
| 54 |
-
if (
|
| 55 |
-
|
| 56 |
-
|
|
|
|
| 57 |
}
|
| 58 |
};
|
| 59 |
|
|
|
|
|
|
|
|
|
|
|
|
|
| 60 |
return (
|
| 61 |
<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)]">
|
| 62 |
<div className="max-w-5xl mx-auto space-y-4">
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 63 |
<textarea
|
| 64 |
className="w-full h-24 bg-black border border-gray-800 text-gray-200 placeholder-gray-600 p-4 rounded-2xl resize-none focus:outline-none focus:border-gray-600 focus:ring-1 focus:ring-gray-600 transition-all text-sm leading-relaxed"
|
| 65 |
placeholder="Paste your brand narrative or campaign copy here"
|
|
@@ -77,10 +98,11 @@ const ChatInput: React.FC<{ onSimulate: (msg: string) => void; onHelpMeCraft: (m
|
|
| 77 |
onChange={handleFileChange}
|
| 78 |
className="hidden"
|
| 79 |
accept="image/*"
|
|
|
|
| 80 |
/>
|
| 81 |
<ChatButton
|
| 82 |
-
label="Upload Images"
|
| 83 |
-
icon={<Image size={14} />}
|
| 84 |
className="h-fit"
|
| 85 |
onClick={handleUploadClick}
|
| 86 |
/>
|
|
@@ -107,39 +129,83 @@ const ChatInput: React.FC<{ onSimulate: (msg: string) => void; onHelpMeCraft: (m
|
|
| 107 |
|
| 108 |
// --- Main Page Component ---
|
| 109 |
|
| 110 |
-
const ChatPage: React.FC<ChatPageProps> = ({ onBack }) => {
|
| 111 |
const [showNotification, setShowNotification] = useState(false);
|
| 112 |
const [isSimulating, setIsSimulating] = useState(false);
|
| 113 |
-
const [
|
| 114 |
|
| 115 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 116 |
setIsSimulating(true);
|
| 117 |
setShowNotification(true);
|
|
|
|
| 118 |
|
| 119 |
-
|
| 120 |
-
|
| 121 |
setIsSimulating(false);
|
| 122 |
-
setSimulationResult(
|
| 123 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 124 |
};
|
| 125 |
|
| 126 |
const handleRefresh = async () => {
|
| 127 |
setIsSimulating(true);
|
| 128 |
try {
|
| 129 |
-
|
| 130 |
-
|
| 131 |
-
|
| 132 |
-
|
| 133 |
-
|
| 134 |
-
|
|
|
|
| 135 |
} catch (error) {
|
| 136 |
setIsSimulating(false);
|
|
|
|
|
|
|
|
|
|
|
|
|
| 137 |
}
|
| 138 |
};
|
| 139 |
|
| 140 |
const handleHelpMeCraft = async (msg: string) => {
|
| 141 |
-
|
| 142 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 143 |
};
|
| 144 |
|
| 145 |
const categories = {
|
|
@@ -175,9 +241,12 @@ const ChatPage: React.FC<ChatPageProps> = ({ onBack }) => {
|
|
| 175 |
<div className="w-8 h-8 flex items-center justify-center bg-gray-800 rounded-lg text-white font-bold">Λ</div>
|
| 176 |
<h2 className="text-xl font-medium tracking-tight">New Simulation</h2>
|
| 177 |
</div>
|
| 178 |
-
<
|
| 179 |
-
<
|
| 180 |
-
|
|
|
|
|
|
|
|
|
|
| 181 |
</div>
|
| 182 |
|
| 183 |
{/* Notification Banner */}
|
|
@@ -188,13 +257,21 @@ const ChatPage: React.FC<ChatPageProps> = ({ onBack }) => {
|
|
| 188 |
<AlertCircle size={18} className="text-green-400" />
|
| 189 |
</div>
|
| 190 |
<div className="flex-1">
|
| 191 |
-
<h4 className="font-semibold text-green-300 mb-1">Simulation
|
| 192 |
<p className="text-sm text-green-200/70 leading-relaxed">
|
| 193 |
-
The simulation
|
| 194 |
</p>
|
| 195 |
{simulationResult && (
|
| 196 |
<div className="mt-4 p-3 bg-black/40 rounded-xl border border-green-500/20 text-xs text-green-200 flex flex-col gap-3">
|
| 197 |
-
<
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 198 |
<button
|
| 199 |
onClick={handleRefresh}
|
| 200 |
disabled={isSimulating}
|
|
@@ -214,7 +291,7 @@ const ChatPage: React.FC<ChatPageProps> = ({ onBack }) => {
|
|
| 214 |
)}
|
| 215 |
|
| 216 |
{/* Scrollable Content Area */}
|
| 217 |
-
<div className="flex-1 overflow-y-auto custom-scrollbar p-6 md:p-8 pb-80">
|
| 218 |
<div className="max-w-5xl mx-auto">
|
| 219 |
<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>
|
| 220 |
|
|
|
|
| 1 |
|
| 2 |
+
import React, { useState, useRef, useEffect } from 'react';
|
| 3 |
+
import { X, ClipboardList, Linkedin, Instagram, Mail, Layout, Edit3, MonitorPlay, Lightbulb, Image, Plus, Sparkles, Zap, AlertCircle, Video, Megaphone, Link as LinkIcon, Loader2, RefreshCw, CheckCircle2 } from 'lucide-react';
|
| 4 |
import { GradioService } from '../services/gradioService';
|
| 5 |
|
| 6 |
// --- Types ---
|
| 7 |
interface ChatPageProps {
|
| 8 |
onBack: () => void;
|
| 9 |
+
simulationResult: any;
|
| 10 |
+
setSimulationResult: (res: any) => void;
|
| 11 |
}
|
| 12 |
|
| 13 |
// --- Sub-components (Modular Structure) ---
|
|
|
|
| 45 |
|
| 46 |
const ChatInput: React.FC<{ onSimulate: (msg: string) => void; onHelpMeCraft: (msg: string) => void; isSimulating: boolean }> = ({ onSimulate, onHelpMeCraft, isSimulating }) => {
|
| 47 |
const [message, setMessage] = useState('');
|
| 48 |
+
const [uploadedFiles, setUploadedFiles] = useState<File[]>([]);
|
| 49 |
const fileInputRef = useRef<HTMLInputElement>(null);
|
| 50 |
|
| 51 |
const handleUploadClick = () => {
|
|
|
|
| 53 |
};
|
| 54 |
|
| 55 |
const handleFileChange = (e: React.ChangeEvent<HTMLInputElement>) => {
|
| 56 |
+
const files = e.target.files;
|
| 57 |
+
if (files && files.length > 0) {
|
| 58 |
+
const newFiles = Array.from(files);
|
| 59 |
+
setUploadedFiles(prev => [...prev, ...newFiles]);
|
| 60 |
+
console.log("Selected files:", newFiles.map(f => f.name));
|
| 61 |
}
|
| 62 |
};
|
| 63 |
|
| 64 |
+
const removeFile = (index: number) => {
|
| 65 |
+
setUploadedFiles(prev => prev.filter((_, i) => i !== index));
|
| 66 |
+
};
|
| 67 |
+
|
| 68 |
return (
|
| 69 |
<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)]">
|
| 70 |
<div className="max-w-5xl mx-auto space-y-4">
|
| 71 |
+
{uploadedFiles.length > 0 && (
|
| 72 |
+
<div className="flex flex-wrap gap-2 mb-2">
|
| 73 |
+
{uploadedFiles.map((file, idx) => (
|
| 74 |
+
<div key={idx} className="flex items-center gap-2 bg-gray-800 border border-gray-700 px-3 py-1.5 rounded-full text-[10px] text-gray-300">
|
| 75 |
+
<Image size={10} />
|
| 76 |
+
<span className="truncate max-w-[100px]">{file.name}</span>
|
| 77 |
+
<button onClick={() => removeFile(idx)} className="text-gray-500 hover:text-white">
|
| 78 |
+
<X size={10} />
|
| 79 |
+
</button>
|
| 80 |
+
</div>
|
| 81 |
+
))}
|
| 82 |
+
</div>
|
| 83 |
+
)}
|
| 84 |
<textarea
|
| 85 |
className="w-full h-24 bg-black border border-gray-800 text-gray-200 placeholder-gray-600 p-4 rounded-2xl resize-none focus:outline-none focus:border-gray-600 focus:ring-1 focus:ring-gray-600 transition-all text-sm leading-relaxed"
|
| 86 |
placeholder="Paste your brand narrative or campaign copy here"
|
|
|
|
| 98 |
onChange={handleFileChange}
|
| 99 |
className="hidden"
|
| 100 |
accept="image/*"
|
| 101 |
+
multiple
|
| 102 |
/>
|
| 103 |
<ChatButton
|
| 104 |
+
label={uploadedFiles.length > 0 ? `${uploadedFiles.length} Images Selected` : "Upload Images"}
|
| 105 |
+
icon={uploadedFiles.length > 0 ? <CheckCircle2 size={14} className="text-green-500" /> : <Image size={14} />}
|
| 106 |
className="h-fit"
|
| 107 |
onClick={handleUploadClick}
|
| 108 |
/>
|
|
|
|
| 129 |
|
| 130 |
// --- Main Page Component ---
|
| 131 |
|
| 132 |
+
const ChatPage: React.FC<ChatPageProps> = ({ onBack, simulationResult, setSimulationResult }) => {
|
| 133 |
const [showNotification, setShowNotification] = useState(false);
|
| 134 |
const [isSimulating, setIsSimulating] = useState(false);
|
| 135 |
+
const [simulationId, setSimulationId] = useState<string>('User Group 1');
|
| 136 |
|
| 137 |
+
useEffect(() => {
|
| 138 |
+
const fetchSimulations = async () => {
|
| 139 |
+
try {
|
| 140 |
+
const sims = await GradioService.listSimulations();
|
| 141 |
+
if (sims && sims.length > 0) {
|
| 142 |
+
// If sims is an array of objects or strings, pick the first one
|
| 143 |
+
const firstSim = typeof sims[0] === 'string' ? sims[0] : (sims[0].id || sims[0].name || 'User Group 1');
|
| 144 |
+
setSimulationId(firstSim);
|
| 145 |
+
}
|
| 146 |
+
} catch (e) {
|
| 147 |
+
console.error("Failed to fetch simulations:", e);
|
| 148 |
+
}
|
| 149 |
+
};
|
| 150 |
+
fetchSimulations();
|
| 151 |
+
}, []);
|
| 152 |
+
|
| 153 |
+
const handleSimulate = async (msg: string) => {
|
| 154 |
+
if (!msg.trim()) {
|
| 155 |
+
alert("Please enter some content to simulate.");
|
| 156 |
+
return;
|
| 157 |
+
}
|
| 158 |
setIsSimulating(true);
|
| 159 |
setShowNotification(true);
|
| 160 |
+
setSimulationResult(null);
|
| 161 |
|
| 162 |
+
try {
|
| 163 |
+
const result = await GradioService.startSimulationAsync(simulationId, msg);
|
| 164 |
setIsSimulating(false);
|
| 165 |
+
setSimulationResult({
|
| 166 |
+
status: "Initiated",
|
| 167 |
+
message: "Simulation started successfully. Please wait for the results.",
|
| 168 |
+
data: result
|
| 169 |
+
});
|
| 170 |
+
} catch (error) {
|
| 171 |
+
setIsSimulating(false);
|
| 172 |
+
setSimulationResult({
|
| 173 |
+
status: "Error",
|
| 174 |
+
message: "Failed to start simulation. Please try again."
|
| 175 |
+
});
|
| 176 |
+
}
|
| 177 |
};
|
| 178 |
|
| 179 |
const handleRefresh = async () => {
|
| 180 |
setIsSimulating(true);
|
| 181 |
try {
|
| 182 |
+
const status = await GradioService.getSimulationStatus(simulationId);
|
| 183 |
+
setIsSimulating(false);
|
| 184 |
+
setSimulationResult({
|
| 185 |
+
status: "Updated",
|
| 186 |
+
message: "Latest status gathered from API.",
|
| 187 |
+
data: status
|
| 188 |
+
});
|
| 189 |
} catch (error) {
|
| 190 |
setIsSimulating(false);
|
| 191 |
+
setSimulationResult({
|
| 192 |
+
status: "Error",
|
| 193 |
+
message: "Failed to gather results. The simulation might still be in progress."
|
| 194 |
+
});
|
| 195 |
}
|
| 196 |
};
|
| 197 |
|
| 198 |
const handleHelpMeCraft = async (msg: string) => {
|
| 199 |
+
if (!msg.trim()) {
|
| 200 |
+
alert("Please enter some content first.");
|
| 201 |
+
return;
|
| 202 |
+
}
|
| 203 |
+
const result = await GradioService.generateVariants(msg);
|
| 204 |
+
if (Array.isArray(result)) {
|
| 205 |
+
alert("Crafted variants:\n\n" + result.join("\n\n"));
|
| 206 |
+
} else {
|
| 207 |
+
alert("Crafted suggestion: " + JSON.stringify(result));
|
| 208 |
+
}
|
| 209 |
};
|
| 210 |
|
| 211 |
const categories = {
|
|
|
|
| 241 |
<div className="w-8 h-8 flex items-center justify-center bg-gray-800 rounded-lg text-white font-bold">Λ</div>
|
| 242 |
<h2 className="text-xl font-medium tracking-tight">New Simulation</h2>
|
| 243 |
</div>
|
| 244 |
+
<div className="flex items-center gap-4">
|
| 245 |
+
{simulationId && <span className="text-[10px] text-gray-500 uppercase tracking-widest hidden md:block">Active Group: {simulationId}</span>}
|
| 246 |
+
<button onClick={onBack} className="p-2 text-gray-500 hover:text-white hover:bg-gray-900 rounded-full transition-colors">
|
| 247 |
+
<X size={24} />
|
| 248 |
+
</button>
|
| 249 |
+
</div>
|
| 250 |
</div>
|
| 251 |
|
| 252 |
{/* Notification Banner */}
|
|
|
|
| 257 |
<AlertCircle size={18} className="text-green-400" />
|
| 258 |
</div>
|
| 259 |
<div className="flex-1">
|
| 260 |
+
<h4 className="font-semibold text-green-300 mb-1">Simulation Status</h4>
|
| 261 |
<p className="text-sm text-green-200/70 leading-relaxed">
|
| 262 |
+
The simulation process can take up to <strong className="text-white">30 minutes</strong>. Click "Gather Results" to fetch the latest state.
|
| 263 |
</p>
|
| 264 |
{simulationResult && (
|
| 265 |
<div className="mt-4 p-3 bg-black/40 rounded-xl border border-green-500/20 text-xs text-green-200 flex flex-col gap-3">
|
| 266 |
+
<div className="font-medium flex items-center gap-2">
|
| 267 |
+
<div className={`w-1.5 h-1.5 rounded-full ${simulationResult.status === 'Error' ? 'bg-red-500' : 'bg-green-500 animate-pulse'}`}></div>
|
| 268 |
+
{simulationResult.message}
|
| 269 |
+
</div>
|
| 270 |
+
{simulationResult.data && (
|
| 271 |
+
<pre className="text-[10px] bg-black/50 p-2 rounded max-h-32 overflow-y-auto custom-scrollbar whitespace-pre-wrap">
|
| 272 |
+
{JSON.stringify(simulationResult.data, null, 2)}
|
| 273 |
+
</pre>
|
| 274 |
+
)}
|
| 275 |
<button
|
| 276 |
onClick={handleRefresh}
|
| 277 |
disabled={isSimulating}
|
|
|
|
| 291 |
)}
|
| 292 |
|
| 293 |
{/* Scrollable Content Area */}
|
| 294 |
+
<div className="flex-1 overflow-y-auto custom-scrollbar p-6 md:p-8 pb-80">
|
| 295 |
<div className="max-w-5xl mx-auto">
|
| 296 |
<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>
|
| 297 |
|
components/SimulationPage.tsx
CHANGED
|
@@ -1,6 +1,7 @@
|
|
| 1 |
import React, { useState, useEffect } from 'react';
|
| 2 |
-
import { ChevronDown, Plus, Info, MessageSquare, BookOpen, LogOut, PanelLeftClose, MessageCircle, AlertTriangle, Menu, PanelRightClose } from 'lucide-react';
|
| 3 |
import SimulationGraph from './SimulationGraph';
|
|
|
|
| 4 |
|
| 5 |
interface SimulationPageProps {
|
| 6 |
onBack: () => void;
|
|
@@ -8,6 +9,8 @@ interface SimulationPageProps {
|
|
| 8 |
onOpenChat: () => void;
|
| 9 |
user?: any;
|
| 10 |
onLogin?: () => void;
|
|
|
|
|
|
|
| 11 |
}
|
| 12 |
|
| 13 |
// Define the data structure for filters
|
|
@@ -40,9 +43,12 @@ const VIEW_FILTERS: Record<string, Array<{ label: string; color: string }>> = {
|
|
| 40 |
]
|
| 41 |
};
|
| 42 |
|
| 43 |
-
const SimulationPage: React.FC<SimulationPageProps> = ({
|
|
|
|
|
|
|
| 44 |
const [society, setSociety] = useState('User Group 1');
|
| 45 |
const [viewMode, setViewMode] = useState('Job Title');
|
|
|
|
| 46 |
const [isBuilding, setIsBuilding] = useState(false);
|
| 47 |
const [isRightPanelOpen, setIsRightPanelOpen] = useState(window.innerWidth > 1200);
|
| 48 |
const [isLeftPanelOpen, setIsLeftPanelOpen] = useState(window.innerWidth > 768);
|
|
@@ -81,7 +87,7 @@ const SimulationPage: React.FC<SimulationPageProps> = ({ onBack, onOpenConversat
|
|
| 81 |
<div className="p-4 h-16 border-b border-gray-800 flex items-center justify-between">
|
| 82 |
<div className="flex items-center gap-2 cursor-pointer" onClick={onBack}>
|
| 83 |
<div className="w-6 h-6 flex items-center justify-center font-bold text-white">Λ</div>
|
| 84 |
-
<span className="font-semibold tracking-tight">
|
| 85 |
</div>
|
| 86 |
<button
|
| 87 |
onClick={() => setIsLeftPanelOpen(false)}
|
|
@@ -258,10 +264,54 @@ const SimulationPage: React.FC<SimulationPageProps> = ({ onBack, onOpenConversat
|
|
| 258 |
</div>
|
| 259 |
<div className="flex-1 overflow-y-auto p-4 space-y-4">
|
| 260 |
<div className="bg-gray-900/50 border border-gray-800 rounded-xl p-4">
|
| 261 |
-
<
|
| 262 |
-
|
| 263 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 264 |
</div>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 265 |
</div>
|
| 266 |
</div>
|
| 267 |
</aside>
|
|
|
|
| 1 |
import React, { useState, useEffect } from 'react';
|
| 2 |
+
import { ChevronDown, Plus, Info, MessageSquare, BookOpen, LogOut, PanelLeftClose, MessageCircle, AlertTriangle, Menu, PanelRightClose, RefreshCw } from 'lucide-react';
|
| 3 |
import SimulationGraph from './SimulationGraph';
|
| 4 |
+
import { GradioService } from '../services/gradioService';
|
| 5 |
|
| 6 |
interface SimulationPageProps {
|
| 7 |
onBack: () => void;
|
|
|
|
| 9 |
onOpenChat: () => void;
|
| 10 |
user?: any;
|
| 11 |
onLogin?: () => void;
|
| 12 |
+
simulationResult: any;
|
| 13 |
+
setSimulationResult: (res: any) => void;
|
| 14 |
}
|
| 15 |
|
| 16 |
// Define the data structure for filters
|
|
|
|
| 43 |
]
|
| 44 |
};
|
| 45 |
|
| 46 |
+
const SimulationPage: React.FC<SimulationPageProps> = ({
|
| 47 |
+
onBack, onOpenConversation, onOpenChat, user, onLogin, simulationResult, setSimulationResult
|
| 48 |
+
}) => {
|
| 49 |
const [society, setSociety] = useState('User Group 1');
|
| 50 |
const [viewMode, setViewMode] = useState('Job Title');
|
| 51 |
+
const [isRefreshing, setIsRefreshing] = useState(false);
|
| 52 |
const [isBuilding, setIsBuilding] = useState(false);
|
| 53 |
const [isRightPanelOpen, setIsRightPanelOpen] = useState(window.innerWidth > 1200);
|
| 54 |
const [isLeftPanelOpen, setIsLeftPanelOpen] = useState(window.innerWidth > 768);
|
|
|
|
| 87 |
<div className="p-4 h-16 border-b border-gray-800 flex items-center justify-between">
|
| 88 |
<div className="flex items-center gap-2 cursor-pointer" onClick={onBack}>
|
| 89 |
<div className="w-6 h-6 flex items-center justify-center font-bold text-white">Λ</div>
|
| 90 |
+
<span className="font-semibold tracking-tight text-xs">Branding Content Testing</span>
|
| 91 |
</div>
|
| 92 |
<button
|
| 93 |
onClick={() => setIsLeftPanelOpen(false)}
|
|
|
|
| 264 |
</div>
|
| 265 |
<div className="flex-1 overflow-y-auto p-4 space-y-4">
|
| 266 |
<div className="bg-gray-900/50 border border-gray-800 rounded-xl p-4">
|
| 267 |
+
<div className="flex items-center justify-between mb-2">
|
| 268 |
+
<p className="text-xs text-gray-500">Simulation Results</p>
|
| 269 |
+
{simulationResult && (
|
| 270 |
+
<button
|
| 271 |
+
onClick={async () => {
|
| 272 |
+
setIsRefreshing(true);
|
| 273 |
+
try {
|
| 274 |
+
const status = await GradioService.getSimulationStatus(society);
|
| 275 |
+
setSimulationResult({
|
| 276 |
+
status: "Updated",
|
| 277 |
+
message: "Latest status gathered from API.",
|
| 278 |
+
data: status
|
| 279 |
+
});
|
| 280 |
+
} catch (e) {
|
| 281 |
+
console.error(e);
|
| 282 |
+
} finally {
|
| 283 |
+
setIsRefreshing(false);
|
| 284 |
+
}
|
| 285 |
+
}}
|
| 286 |
+
className="p-1 hover:bg-gray-800 rounded text-gray-500 hover:text-white"
|
| 287 |
+
title="Refresh Status"
|
| 288 |
+
>
|
| 289 |
+
<RefreshCw size={12} className={isRefreshing ? "animate-spin" : ""} />
|
| 290 |
+
</button>
|
| 291 |
+
)}
|
| 292 |
</div>
|
| 293 |
+
|
| 294 |
+
{!simulationResult ? (
|
| 295 |
+
<div className="text-sm text-gray-400 italic text-center py-8">
|
| 296 |
+
Results will appear here after running a simulation.
|
| 297 |
+
</div>
|
| 298 |
+
) : (
|
| 299 |
+
<div className="space-y-3">
|
| 300 |
+
<div className="flex items-center gap-2 text-xs font-medium text-green-400">
|
| 301 |
+
<div className="w-1.5 h-1.5 rounded-full bg-green-500 animate-pulse"></div>
|
| 302 |
+
{simulationResult.status}
|
| 303 |
+
</div>
|
| 304 |
+
<p className="text-[11px] text-gray-400">{simulationResult.message}</p>
|
| 305 |
+
{simulationResult.data && (
|
| 306 |
+
<pre className="text-[10px] bg-black/50 p-2 rounded max-h-96 overflow-y-auto custom-scrollbar whitespace-pre-wrap text-gray-300 border border-gray-800">
|
| 307 |
+
{JSON.stringify(simulationResult.data, null, 2)}
|
| 308 |
+
</pre>
|
| 309 |
+
)}
|
| 310 |
+
<p className="text-[10px] text-gray-600 mt-4 italic">
|
| 311 |
+
Note: Complete simulation results can take up to 30 minutes to be fully processed by the API.
|
| 312 |
+
</p>
|
| 313 |
+
</div>
|
| 314 |
+
)}
|
| 315 |
</div>
|
| 316 |
</div>
|
| 317 |
</aside>
|
inspect_gradio.cjs
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
|
| 2 |
+
const { Client } = require("@gradio/client");
|
| 3 |
+
|
| 4 |
+
async function inspect() {
|
| 5 |
+
try {
|
| 6 |
+
const client = await Client.connect("AUXteam/tiny_factory");
|
| 7 |
+
console.log("Keys:", Object.keys(client));
|
| 8 |
+
if (client.view_api) {
|
| 9 |
+
const api = await client.view_api();
|
| 10 |
+
console.log(JSON.stringify(api, null, 2));
|
| 11 |
+
}
|
| 12 |
+
} catch (e) {
|
| 13 |
+
console.error(e);
|
| 14 |
+
}
|
| 15 |
+
}
|
| 16 |
+
|
| 17 |
+
inspect();
|
inspect_gradio.ts
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
|
| 2 |
+
import { Client } from "@gradio/client";
|
| 3 |
+
|
| 4 |
+
async function inspect() {
|
| 5 |
+
const client = await Client.connect("AUXteam/tiny_factory");
|
| 6 |
+
console.log(JSON.stringify(client.config.endpoints, null, 2));
|
| 7 |
+
}
|
| 8 |
+
|
| 9 |
+
inspect();
|
server.cjs
CHANGED
|
@@ -5,6 +5,13 @@ const port = 7860;
|
|
| 5 |
|
| 6 |
app.use(express.static(path.join(__dirname, 'dist')));
|
| 7 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 8 |
app.get('/health', (req, res) => {
|
| 9 |
res.status(200).send('OK');
|
| 10 |
});
|
|
|
|
| 5 |
|
| 6 |
app.use(express.static(path.join(__dirname, 'dist')));
|
| 7 |
|
| 8 |
+
app.get('/api/config', (req, res) => {
|
| 9 |
+
res.json({
|
| 10 |
+
clientId: process.env.OAUTH_CLIENT_ID,
|
| 11 |
+
scopes: process.env.OAUTH_SCOPES || "openid profile",
|
| 12 |
+
});
|
| 13 |
+
});
|
| 14 |
+
|
| 15 |
app.get('/health', (req, res) => {
|
| 16 |
res.status(200).send('OK');
|
| 17 |
});
|
services/gradioService.ts
CHANGED
|
@@ -23,25 +23,47 @@ export class GradioService {
|
|
| 23 |
}
|
| 24 |
}
|
| 25 |
|
| 26 |
-
static async
|
| 27 |
try {
|
| 28 |
const client = await this.getClient();
|
| 29 |
-
const result = await client.predict("/
|
| 30 |
return result.data;
|
| 31 |
} catch (error) {
|
| 32 |
-
console.error("Error
|
| 33 |
throw error;
|
| 34 |
}
|
| 35 |
}
|
| 36 |
|
| 37 |
-
static async
|
| 38 |
try {
|
| 39 |
const client = await this.getClient();
|
| 40 |
-
const result = await client.predict("/
|
| 41 |
return result.data;
|
| 42 |
} catch (error) {
|
| 43 |
-
console.error("Error
|
| 44 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 45 |
}
|
| 46 |
}
|
| 47 |
}
|
|
|
|
| 23 |
}
|
| 24 |
}
|
| 25 |
|
| 26 |
+
static async startSimulationAsync(simulationId: string, contentText: string, format: string = "text") {
|
| 27 |
try {
|
| 28 |
const client = await this.getClient();
|
| 29 |
+
const result = await client.predict("/start_simulation_async", [simulationId, contentText, format]);
|
| 30 |
return result.data;
|
| 31 |
} catch (error) {
|
| 32 |
+
console.error("Error starting simulation:", error);
|
| 33 |
throw error;
|
| 34 |
}
|
| 35 |
}
|
| 36 |
|
| 37 |
+
static async getSimulationStatus(simulationId: string) {
|
| 38 |
try {
|
| 39 |
const client = await this.getClient();
|
| 40 |
+
const result = await client.predict("/get_simulation_status", [simulationId]);
|
| 41 |
return result.data;
|
| 42 |
} catch (error) {
|
| 43 |
+
console.error("Error getting simulation status:", error);
|
| 44 |
+
throw error;
|
| 45 |
+
}
|
| 46 |
+
}
|
| 47 |
+
|
| 48 |
+
static async generateVariants(contentText: string, numVariants: number = 3) {
|
| 49 |
+
try {
|
| 50 |
+
const client = await this.getClient();
|
| 51 |
+
const result = await client.predict("/generate_variants", [contentText, numVariants]);
|
| 52 |
+
return result.data;
|
| 53 |
+
} catch (error) {
|
| 54 |
+
console.error("Error generating variants:", error);
|
| 55 |
+
return ["Unable to generate variants at this time."];
|
| 56 |
+
}
|
| 57 |
+
}
|
| 58 |
+
|
| 59 |
+
static async listSimulations() {
|
| 60 |
+
try {
|
| 61 |
+
const client = await this.getClient();
|
| 62 |
+
const result = await client.predict("/list_simulations", []);
|
| 63 |
+
return result.data;
|
| 64 |
+
} catch (error) {
|
| 65 |
+
console.error("Error listing simulations:", error);
|
| 66 |
+
return [];
|
| 67 |
}
|
| 68 |
}
|
| 69 |
}
|