← all conversations

Chrome Extension Broadcast Generator

2025-06-307 turns8,798 charsgpt-4o
chrome-extensionbroadcast-generatorpersona-generation

Summary

The user wants to convert a GitHub repository into a Chrome extension that generates personas from writing samples for use in a broadcast generator.

Messages

let's take github.com/kliewerdaniel/infinitebroadcast01.git and make it into a Chrome extension for an advanced UI which allows you to upload a writing sample to generate a persona to be used by the broadcast generator
⚙ model reasoning step
gpt-4o
⚙ model reasoning step
gpt-4o
⚙ model reasoning step
gpt-4o
⚙ model reasoning step
gpt-4o
⚙ model reasoning step
gpt-4o
Below is a practical “blue-print” for turning **`infinitebroadcast01`** into a Manifest V3 Chrome extension with an advanced React UI that lets the user upload a writing sample, turns it into a persona JSON, and feeds that persona into the existing broadcast-generator logic. Where I give code, you can copy-paste it as-is; where you see 💡, it’s a decision point you can adjust to your needs. --- ## 1. Project layout (high-level) ``` infinitebroadcast-ext/ ├── extension/ # Everything that ships to Chrome │ ├── manifest.json │ ├── service_worker.js # Wraps your generator & persona logic │ ├── offscreen.html # DOM access for file-parsing, optional │ ├── ui/ # React bundle │ │ ├── index.html │ │ └── src/ │ │ ├── App.jsx │ │ ├── UploadPersona.jsx │ │ └── BroadcastPreview.jsx └── core/ # Re-usable JS library extracted from the repo ├── persona.js # generatePersona(text) ⇒ persona JSON └── broadcast.js # generateBroadcast(persona, …) ``` **Why split out `core/`?** Chrome service-workers can’t use Node APIs or large native deps. Extracting pure-JS logic into a separate package lets you (a) unit-test outside Chrome, (b) reuse in server or CLI tools. *(If the current repo is Python/Rust/etc., port those bits that must run in-browser to JS; keep the heavy lifting on a localhost API and call it from the service-worker instead.)* --- ## 2. Manifest V3 (`extension/manifest.json`) ```jsonc { "manifest_version": 3, "name": "Infinite Broadcast Creator", "description": "Upload a writing sample, craft a persona, and auto-generate infinite broadcasts.", "version": "0.1.0", "action": { "default_title": "Infinite Broadcast", "default_popup": "ui/index.html" }, "icons": { "16": "assets/icon16.png", "48": "assets/icon48.png", "128": "assets/icon128.png" }, "background": { "service_worker": "service_worker.js", "type": "module" }, "host_permissions": [ "http://localhost:11434/*" /* ← if you’re talking to Ollama or any local API */ ], "permissions": [ "storage", "offscreen", /* needed only if you use an off-screen doc */ "scripting" ] } ``` *Manifest V2 is fully disabled for consumer Chrome as of March 31 2025, so MV3 is mandatory.* citeturn2search3 --- ## 3. Service worker (`extension/service_worker.js`) ```js import { generatePersona } from '../core/persona.js'; import { generateBroadcast } from '../core/broadcast.js'; /* 1️⃣ Handle messages from the UI */ chrome.runtime.onMessage.addListener(async (msg, sender, sendResponse) => { if (msg.type === 'BUILD_PERSONA') { const persona = await generatePersona(msg.payload.text); await chrome.storage.local.set({ persona }); sendResponse({ ok: true, persona }); } if (msg.type === 'RUN_BROADCAST') { const { persona, config } = await chrome.storage.local.get(['persona', 'config']); const episode = await generateBroadcast(persona, config); sendResponse({ ok: true, episode }); } return true; // keep channel open for async response }); /* 2️⃣ (Opt.) keep SW alive while generator runs */ chrome.runtime.onInstalled.addListener(() => { chrome.alarms.create('keepAlive', { periodInMinutes: 4.5 }); }); chrome.alarms.onAlarm.addListener(alarm => { if (alarm.name === 'keepAlive') void 0; // noop ping }); ``` --- ## 4. React UI (bundled into `extension/ui/`) ### `App.jsx` ```jsx import { useState } from 'react'; import UploadPersona from './UploadPersona'; import BroadcastPreview from './BroadcastPreview'; export default function App() { const [persona, setPersona] = useState(null); const [episode, setEpisode] = useState(null); async function handlePersona(fileText) { const { persona } = await chrome.runtime.sendMessage({ type: 'BUILD_PERSONA', payload: { text: fileText } }); setPersona(persona); } async function runBroadcast() { const { episode } = await chrome.runtime.sendMessage({ type: 'RUN_BROADCAST' }); setEpisode(episode); } return ( <main className="flex flex-col gap-4 p-4 w-[400px]"> <UploadPersona onUpload={handlePersona} /> {persona && ( <> <pre className="bg-slate-100 p-2 rounded overflow-x-auto">{JSON.stringify(persona, null, 2)}</pre> <button className="btn" onClick={runBroadcast}>Generate Broadcast</button> </> )} {episode && <BroadcastPreview episode={episode} />} </main> ); } ``` ### `UploadPersona.jsx` ```jsx export default function UploadPersona({ onUpload }) { function handleChange(e) { const file = e.target.files?.[0]; if (!file) return; const reader = new FileReader(); reader.onload = evt => onUpload(evt.target.result); reader.readAsText(file); } return ( <label className="flex flex-col items-center gap-2 p-4 border border-dashed rounded cursor-pointer"> <span>📄 Choose a writing sample</span> <input type="file" accept=".txt,.md" onChange={handleChange} className="hidden" /> </label> ); } ``` *(Any styling library works; Tailwind is simplest in an extension if you build with Vite.)* --- ## 5. Persona generation (`core/persona.js`) Keep it **pure JS** so it can run in either a Chrome SW or Node script: ```js import { OpenAI } from 'openai'; // or your local client const systemPrompt = ` You are a persona-extractor that builds a JSON profile with fields: name, tone_formal, tone_informal, humor_dry, favorite_topics[] ... Return *only* valid JSON. `; export async function generatePersona(text) { const openai = new OpenAI({ baseURL: 'http://localhost:11434/v1' }); // 💡 const { choices } = await openai.chat.completions.create({ model: 'your-model', messages: [ { role: 'system', content: systemPrompt }, { role: 'user', content: `Here is the writing sample:\n\n${text}` } ] }); return JSON.parse(choices[0].message.content); } ``` --- ## 6. Broadcast generation (`core/broadcast.js`) Wrap whatever logic already exists in the repo: ```js export async function generateBroadcast(persona, cfg = {}) { // Your existing infinitebroadcast01 logic, refactored // so it takes (persona, cfg) and returns a string or object } ``` If that logic *cannot* run in-browser (e.g. uses heavy Python libs), expose a REST endpoint locally (FastAPI, Flask, etc.). Then in the SW replace the direct import with a `fetch('http://localhost:8000/broadcast', {…})`. --- ## 7. Bundling & Dev workflow 1. `pnpm create vite extension-ui --template react` (or `npm init @vitejs/app`). 2. In `vite.config.js` add: ```js export default defineConfig({ build: { outDir: '../extension/ui', emptyOutDir: true }, plugins: [react()], }); ``` 3. Top-level `package.json`: ```jsonc { "scripts": { "dev": "vite --watch", "build": "vite build && cp -r extension ../../dist" } } ``` 4. **Load the unpacked extension**: `chrome://extensions` → “Load unpacked” → select `extension/`. --- ## 8. Persisting state & settings - Use `chrome.storage.sync` for small persona JSONs (<100 KB) so the user’s profile follows them across devices. - Store large generated episodes in `chrome.storage.local`. - Save last-used config under key `config` and expose it in an “Options” page (same React app routed to `/options.html`). --- ## 9. Advanced UI ideas | UX Feature | Quick-start hint | |------------|-----------------| | Drag-and-drop area | Wrap `<Dropzone>` around `<UploadPersona>` using `react-dropzone` | | Persona radar chart | Use `recharts` in `BroadcastPreview.jsx` | | Dark mode toggle | Tailwind’s `dark:` variants + persist setting in `storage.sync` | | Offline TTS preview | Use Chrome’s **`tts.speak()`** API (MV3-safe) to read the broadcast aloud | --- ## 10. Testing checklist 1. **Unit tests** in `core/` with Vitest or Jest. 2. **E2E**: Load extension → DevTools “Service Worker” tab → simulate file upload → inspect network calls. 3. Verify the SW hibernates / wakes correctly (alarm keep-alive is enough for <5 min tasks). 4. Confirm only required permissions appear in the install prompt (users are sensitive post-MV3). --- ### Next steps * Clone-and-separate the current repo’s persona & broadcast code into `core/`. * Drop the scaffolds above into `extension/` and `ui/`, run `npm run dev`, and iterate. * When you’re ready for me to fill in any of the placeholder logic—or if you’d like the full code base in a canvas so we can tweak it line-by-line—just let me know!
gpt-4o