Chrome Extension Broadcast Generator
2025-06-307 turns8,798 charsgpt-4o
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.* citeturn2search3
---
## 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