factorstudios commited on
Commit
02812c3
Β·
verified Β·
1 Parent(s): 70ac245

Update main.py

Browse files
Files changed (1) hide show
  1. main.py +258 -19
main.py CHANGED
@@ -16,6 +16,9 @@ from pydantic import BaseModel
16
  from typing import Optional
17
  from orchestrator import PipelineOrchestrator
18
  from config import settings
 
 
 
19
 
20
 
21
  # Configure logging
@@ -32,8 +35,10 @@ app = FastAPI(
32
  version="1.0.0",
33
  )
34
 
35
- # Initialize orchestrator
36
  orchestrator = PipelineOrchestrator()
 
 
37
 
38
 
39
  # Request/Response Models
@@ -79,25 +84,10 @@ async def health_check():
79
  }
80
 
81
 
82
- @app.get("/")
83
  async def root():
84
- """Root endpoint with API documentation."""
85
- return {
86
- "service": "Multi-Agent Content Generation System",
87
- "version": "1.0.0",
88
- "port": settings.port,
89
- "docs": "/docs",
90
- "agents": {
91
- "showrunner": "/api/agents/showrunner",
92
- "story_editor": "/api/agents/story-editor",
93
- "cultural_consultant": "/api/agents/cultural-consultant",
94
- "lead_writer": "/api/agents/lead-writer",
95
- "dialogue_specialist": "/api/agents/dialogue-specialist",
96
- "comedy_writer": "/api/agents/comedy-writer",
97
- "proofreader": "/api/agents/proofreader",
98
- },
99
- "pipeline": "/api/pipeline/execute",
100
- }
101
 
102
 
103
  # ─────────────────────────────────────────────────────────────────────────────
@@ -170,6 +160,255 @@ async def get_pipeline_status(run_id: str):
170
  raise HTTPException(status_code=500, detail=str(e))
171
 
172
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
173
  # ─────────────────────────────────────────────────────────────────────────────
174
  # AGENT ENDPOINTS (Port 7860)
175
  # ─────────────────────────────────────────────────────────────────────────────
 
16
  from typing import Optional
17
  from orchestrator import PipelineOrchestrator
18
  from config import settings
19
+ from instructor import Instructor
20
+ from fastapi.responses import HTMLResponse
21
+ import requests
22
 
23
 
24
  # Configure logging
 
35
  version="1.0.0",
36
  )
37
 
38
+ # Initialize orchestrator and instructor
39
  orchestrator = PipelineOrchestrator()
40
+ instructor = Instructor()
41
+ latest_briefs = {} # Store by session or just global for now
42
 
43
 
44
  # Request/Response Models
 
84
  }
85
 
86
 
87
+ @app.get("/", response_class=HTMLResponse)
88
  async def root():
89
+ """Root endpoint redirects to Instructor UI."""
90
+ return await get_instructor_ui()
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
91
 
92
 
93
  # ─────────────────────────────────────────────────────────────────────────────
 
160
  raise HTTPException(status_code=500, detail=str(e))
161
 
162
 
163
+ # ─────────────────────────────────────────────────────────────────────────────
164
+ # INSTRUCTOR ENDPOINTS (Port 7860)
165
+ # ─────────────────────────────────────────────────────────────────────────────
166
+
167
+ @app.get("/instructor", response_class=HTMLResponse)
168
+ async def get_instructor_ui():
169
+ """Instructor UI for brief approval."""
170
+ html_content = """
171
+ <!DOCTYPE html>
172
+ <html lang="en">
173
+ <head>
174
+ <meta charset="UTF-8">
175
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
176
+ <title>Instructor Module - Brief Approval</title>
177
+ <script src="https://cdn.tailwindcss.com"></script>
178
+ <style>
179
+ .loading { display: none; }
180
+ .loading.active { display: flex; }
181
+ </style>
182
+ </head>
183
+ <body class="bg-gray-100 min-h-screen p-8">
184
+ <div class="max-w-4xl mx-auto bg-white rounded-xl shadow-md overflow-hidden p-6">
185
+ <h1 class="text-3xl font-bold text-gray-800 mb-6">Instructor Module</h1>
186
+
187
+ <div id="setup-section" class="mb-8">
188
+ <h2 class="text-xl font-semibold mb-4">Generate New Brief</h2>
189
+ <div class="flex flex-col gap-4">
190
+ <p class="text-sm text-gray-600">Enter trending topics or news items to fuse into a storytelling brief.</p>
191
+ <div class="flex gap-4">
192
+ <input type="text" id="topics-input" placeholder="e.g. AI agents, TikTok trends, Summer Olympics"
193
+ class="flex-1 p-2 border rounded-lg focus:ring-2 focus:ring-blue-500 outline-none">
194
+ <button onclick="generateBrief()" id="gen-btn"
195
+ class="bg-blue-600 text-white px-6 py-2 rounded-lg hover:bg-blue-700 transition">
196
+ Generate
197
+ </button>
198
+ </div>
199
+ </div>
200
+ </div>
201
+
202
+ <div id="loading-spinner" class="loading flex-col items-center justify-center py-12">
203
+ <div class="animate-spin rounded-full h-12 w-12 border-b-2 border-blue-600"></div>
204
+ <span class="ml-4 mt-4 text-gray-600">Instructor is fusing trends into a story...</span>
205
+ </div>
206
+
207
+ <div id="brief-section" class="hidden">
208
+ <div class="flex justify-between items-center mb-4">
209
+ <h2 class="text-xl font-semibold text-blue-600">Proposed Storytelling Brief</h2>
210
+ <span class="bg-blue-100 text-blue-800 text-xs font-medium px-2.5 py-0.5 rounded">LLM Generated</span>
211
+ </div>
212
+ <div id="brief-content" class="space-y-4 bg-gray-50 p-6 rounded-lg border border-gray-200 mb-6 max-h-[500px] overflow-y-auto">
213
+ <!-- Brief fields will be injected here -->
214
+ </div>
215
+ <div class="flex gap-4">
216
+ <button onclick="approveBrief(true)" id="approve-btn"
217
+ class="bg-green-600 text-white px-8 py-3 rounded-lg font-bold hover:bg-green-700 transition flex-1 shadow-lg">
218
+ Approve & Execute Pipeline
219
+ </button>
220
+ <button onclick="approveBrief(false)"
221
+ class="bg-gray-400 text-white px-8 py-3 rounded-lg font-bold hover:bg-gray-500 transition">
222
+ Discard
223
+ </button>
224
+ </div>
225
+ </div>
226
+
227
+ <div id="status-section" class="mt-8 p-4 rounded-lg hidden">
228
+ <p id="status-message" class="font-medium"></p>
229
+ <div id="status-details" class="mt-2 text-sm"></div>
230
+ </div>
231
+ </div>
232
+
233
+ <script>
234
+ let currentBrief = null;
235
+
236
+ async function generateBrief() {
237
+ const topicsInput = document.getElementById('topics-input').value;
238
+ const topics = topicsInput.split(',').map(t => t.trim()).filter(t => t);
239
+
240
+ if (topics.length === 0) {
241
+ alert('Please enter at least one topic');
242
+ return;
243
+ }
244
+
245
+ showLoading(true);
246
+ document.getElementById('brief-section').classList.add('hidden');
247
+ document.getElementById('status-section').classList.add('hidden');
248
+
249
+ try {
250
+ const response = await fetch('/api/instructor/generate', {
251
+ method: 'POST',
252
+ headers: { 'Content-Type': 'application/json' },
253
+ body: JSON.stringify({ topics })
254
+ });
255
+
256
+ const data = await response.json();
257
+ if (data.status === 'success') {
258
+ currentBrief = data.brief;
259
+ displayBrief(currentBrief);
260
+ } else {
261
+ throw new Error(data.detail || 'Generation failed');
262
+ }
263
+ } catch (error) {
264
+ showStatus('Error generating brief: ' + error.message, 'bg-red-100 text-red-700');
265
+ } finally {
266
+ showLoading(false);
267
+ }
268
+ }
269
+
270
+ function displayBrief(brief) {
271
+ const content = document.getElementById('brief-content');
272
+ content.innerHTML = '';
273
+
274
+ const fieldOrder = [
275
+ 'user_brief', 'hook_brief', 'style_guide',
276
+ 'character_bible', 'world_building_document',
277
+ 'season_arc_document', 'character_voice_guide', 'continuity_log'
278
+ ];
279
+
280
+ fieldOrder.forEach(key => {
281
+ if (brief[key]) {
282
+ const div = document.createElement('div');
283
+ div.className = 'mb-6 border-b border-gray-200 pb-4 last:border-0';
284
+ div.innerHTML = `
285
+ <h3 class="text-xs font-bold text-blue-500 uppercase tracking-widest mb-1">${key.replace(/_/g, ' ')}</h3>
286
+ <p class="text-gray-800 leading-relaxed">${brief[key]}</p>
287
+ `;
288
+ content.appendChild(div);
289
+ }
290
+ });
291
+
292
+ document.getElementById('brief-section').classList.remove('hidden');
293
+ content.scrollTop = 0;
294
+ }
295
+
296
+ async function approveBrief(approved) {
297
+ if (!approved) {
298
+ document.getElementById('brief-section').classList.add('hidden');
299
+ return;
300
+ }
301
+
302
+ const btn = document.getElementById('approve-btn');
303
+ btn.disabled = true;
304
+ btn.innerHTML = `
305
+ <svg class="animate-spin -ml-1 mr-3 h-5 w-5 text-white inline" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24">
306
+ <circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4"></circle>
307
+ <path class="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"></path>
308
+ </svg>
309
+ Executing Pipeline...
310
+ `;
311
+
312
+ try {
313
+ const response = await fetch('/api/instructor/approve', {
314
+ method: 'POST',
315
+ headers: { 'Content-Type': 'application/json' },
316
+ body: JSON.stringify({ approved: true })
317
+ });
318
+
319
+ const data = await response.json();
320
+ if (response.ok) {
321
+ showStatus('Success! Pipeline execution started.', 'bg-green-100 text-green-700');
322
+ const details = document.getElementById('status-details');
323
+ details.innerHTML = `
324
+ <p><strong>Run ID:</strong> ${data.run_id}</p>
325
+ <p class="mt-2"><a href="${data.hf_output_url}" target="_blank" class="text-blue-600 underline">View Output on Hugging Face</a></p>
326
+ `;
327
+ document.getElementById('brief-section').classList.add('hidden');
328
+ } else {
329
+ showStatus('Error: ' + (data.detail || 'Failed to start pipeline'), 'bg-red-100 text-red-700');
330
+ }
331
+ } catch (error) {
332
+ showStatus('Connection error: ' + error.message, 'bg-red-100 text-red-700');
333
+ } finally {
334
+ btn.disabled = false;
335
+ btn.innerText = 'Approve & Execute Pipeline';
336
+ }
337
+ }
338
+
339
+ function showLoading(show) {
340
+ document.getElementById('loading-spinner').classList.toggle('active', show);
341
+ document.getElementById('gen-btn').disabled = show;
342
+ }
343
+
344
+ function showStatus(msg, classes) {
345
+ const section = document.getElementById('status-section');
346
+ section.className = 'mt-8 p-4 rounded-lg ' + classes;
347
+ document.getElementById('status-message').innerText = msg;
348
+ section.classList.remove('hidden');
349
+ }
350
+ </script>
351
+ </body>
352
+ </html>
353
+ """
354
+ return HTMLResponse(content=html_content)
355
+
356
+ @app.post("/api/instructor/generate")
357
+ async def api_generate_brief(request: dict):
358
+ """API endpoint to generate a brief from trending topics."""
359
+ try:
360
+ topics = request.get("topics", [])
361
+ if not topics:
362
+ raise HTTPException(status_code=400, detail="No topics provided")
363
+
364
+ logger.info(f"[INSTRUCTOR] Generating brief for topics: {topics}")
365
+ brief = instructor.generate_brief(topics)
366
+
367
+ # Store the brief for approval (using a simple global for this demo)
368
+ global latest_briefs
369
+ latest_briefs["current"] = brief
370
+
371
+ return {"status": "success", "brief": brief}
372
+ except Exception as e:
373
+ logger.error(f"[INSTRUCTOR] Error: {str(e)}")
374
+ raise HTTPException(status_code=500, detail=str(e))
375
+
376
+ @app.post("/api/instructor/approve")
377
+ async def api_approve_brief(request: dict):
378
+ """API endpoint to approve and send the brief to the orchestrator."""
379
+ if not request.get("approved"):
380
+ return {"status": "discarded"}
381
+
382
+ global latest_briefs
383
+ brief = latest_briefs.get("current")
384
+
385
+ if not brief:
386
+ raise HTTPException(status_code=400, detail="No brief available to approve")
387
+
388
+ try:
389
+ logger.info("[INSTRUCTOR] Brief approved, executing pipeline")
390
+
391
+ # We can call the orchestrator directly since we're in the same process
392
+ result = orchestrator.execute_pipeline(
393
+ user_brief=brief["user_brief"],
394
+ season_arc_document=brief["season_arc_document"],
395
+ character_bible=brief["character_bible"],
396
+ world_building_document=brief["world_building_document"],
397
+ character_voice_guide=brief["character_voice_guide"],
398
+ style_guide=brief["style_guide"],
399
+ continuity_log=brief["continuity_log"],
400
+ hook_brief=brief.get("hook_brief"),
401
+ )
402
+
403
+ return {
404
+ "status": "success",
405
+ "run_id": result["run_id"],
406
+ "hf_output_url": result["hf_output_url"]
407
+ }
408
+ except Exception as e:
409
+ logger.error(f"[INSTRUCTOR] Pipeline error: {str(e)}")
410
+ raise HTTPException(status_code=500, detail=str(e))
411
+
412
  # ─────────────────────────────────────────────────────────────────────────────
413
  # AGENT ENDPOINTS (Port 7860)
414
  # ─────────────────────────────────────────────────────────────────────────────