mirror of
https://github.com/getcompanion-ai/co-mono.git
synced 2026-04-16 14:01:06 +00:00
feat: add cross-browser extension with AI reading assistant
- Create Pi Reader browser extension for Chrome/Firefox - Chrome uses Side Panel API, Firefox uses Sidebar Action API - Supports both browsers with separate manifests and unified codebase - Built with mini-lit components and Tailwind CSS v4 - Features model selection dialog with Ollama support - Hot reload development server watches both browser builds - Add useDefineForClassFields: false to fix LitElement reactivity
This commit is contained in:
parent
c1185c7b95
commit
b67c10dfb1
33 changed files with 4453 additions and 1202 deletions
84
packages/browser-extension/scripts/build.mjs
Normal file
84
packages/browser-extension/scripts/build.mjs
Normal file
|
|
@ -0,0 +1,84 @@
|
|||
import { build, context } from "esbuild";
|
||||
import { copyFileSync, mkdirSync, rmSync } from "node:fs";
|
||||
import { dirname, join } from "node:path";
|
||||
import { fileURLToPath } from "node:url";
|
||||
|
||||
const __filename = fileURLToPath(import.meta.url);
|
||||
const __dirname = dirname(__filename);
|
||||
const packageRoot = join(__dirname, "..");
|
||||
const isWatch = process.argv.includes("--watch");
|
||||
|
||||
// Determine target browser from command line arguments
|
||||
const targetBrowser = process.argv.includes("--firefox") ? "firefox" : "chrome";
|
||||
const outDir = join(packageRoot, `dist-${targetBrowser}`);
|
||||
|
||||
const entryPoints = {
|
||||
sidepanel: join(packageRoot, "src/sidepanel.ts"),
|
||||
background: join(packageRoot, "src/background.ts")
|
||||
};
|
||||
|
||||
rmSync(outDir, { recursive: true, force: true });
|
||||
mkdirSync(outDir, { recursive: true });
|
||||
|
||||
const buildOptions = {
|
||||
absWorkingDir: packageRoot,
|
||||
entryPoints,
|
||||
bundle: true,
|
||||
outdir: outDir,
|
||||
format: "esm",
|
||||
target: targetBrowser === "firefox" ? ["firefox115"] : ["chrome120"],
|
||||
platform: "browser",
|
||||
sourcemap: isWatch ? "inline" : true,
|
||||
entryNames: "[name]",
|
||||
loader: {
|
||||
".ts": "ts",
|
||||
".tsx": "tsx"
|
||||
},
|
||||
define: {
|
||||
"process.env.NODE_ENV": JSON.stringify(process.env.NODE_ENV ?? (isWatch ? "development" : "production")),
|
||||
"process.env.TARGET_BROWSER": JSON.stringify(targetBrowser)
|
||||
}
|
||||
};
|
||||
|
||||
const copyStatic = () => {
|
||||
// Use browser-specific manifest
|
||||
const manifestSource = join(packageRoot, `manifest.${targetBrowser}.json`);
|
||||
const manifestDest = join(outDir, "manifest.json");
|
||||
copyFileSync(manifestSource, manifestDest);
|
||||
|
||||
// Copy other static files
|
||||
const filesToCopy = [
|
||||
"icon-16.png",
|
||||
"icon-48.png",
|
||||
"icon-128.png",
|
||||
join("src", "sidepanel.html")
|
||||
];
|
||||
|
||||
for (const relative of filesToCopy) {
|
||||
const source = join(packageRoot, relative);
|
||||
let destination = join(outDir, relative);
|
||||
if (relative.startsWith("src/")) {
|
||||
destination = join(outDir, relative.slice(4)); // Remove "src/" prefix
|
||||
}
|
||||
copyFileSync(source, destination);
|
||||
}
|
||||
|
||||
console.log(`Built for ${targetBrowser} in ${outDir}`);
|
||||
};
|
||||
|
||||
const run = async () => {
|
||||
if (isWatch) {
|
||||
const ctx = await context(buildOptions);
|
||||
await ctx.watch();
|
||||
copyStatic();
|
||||
process.stdout.write("Watching for changes...\n");
|
||||
} else {
|
||||
await build(buildOptions);
|
||||
copyStatic();
|
||||
}
|
||||
};
|
||||
|
||||
run().catch((error) => {
|
||||
console.error(error);
|
||||
process.exitCode = 1;
|
||||
});
|
||||
83
packages/browser-extension/scripts/dev-server.mjs
Normal file
83
packages/browser-extension/scripts/dev-server.mjs
Normal file
|
|
@ -0,0 +1,83 @@
|
|||
import { createServer } from "http";
|
||||
import { WebSocketServer } from "ws";
|
||||
import { watch } from "fs";
|
||||
import { join, dirname } from "path";
|
||||
import { fileURLToPath } from "url";
|
||||
|
||||
const __filename = fileURLToPath(import.meta.url);
|
||||
const __dirname = dirname(__filename);
|
||||
|
||||
// Watch both browser directories
|
||||
const distDirChrome = join(__dirname, "..", "dist-chrome");
|
||||
const distDirFirefox = join(__dirname, "..", "dist-firefox");
|
||||
|
||||
const PORT = 8765; // Fixed port for WebSocket server
|
||||
const server = createServer();
|
||||
const wss = new WebSocketServer({ server });
|
||||
|
||||
const clients = new Set();
|
||||
|
||||
// WebSocket connection handling
|
||||
wss.on("connection", (ws) => {
|
||||
console.log("[DevServer] Client connected");
|
||||
clients.add(ws);
|
||||
|
||||
ws.on("close", () => {
|
||||
console.log("[DevServer] Client disconnected");
|
||||
clients.delete(ws);
|
||||
});
|
||||
|
||||
ws.on("error", (error) => {
|
||||
console.error("[DevServer] WebSocket error:", error);
|
||||
clients.delete(ws);
|
||||
});
|
||||
|
||||
// Send initial connection confirmation
|
||||
ws.send(JSON.stringify({ type: "connected" }));
|
||||
});
|
||||
|
||||
// Watch for changes in both dist directories
|
||||
const watcherChrome = watch(distDirChrome, { recursive: true }, (eventType, filename) => {
|
||||
if (filename) {
|
||||
console.log(`[DevServer] Chrome file changed: ${filename}`);
|
||||
|
||||
// Send reload message to all connected clients
|
||||
const message = JSON.stringify({ type: "reload", browser: "chrome", file: filename });
|
||||
clients.forEach((client) => {
|
||||
if (client.readyState === 1) { // OPEN state
|
||||
client.send(message);
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
const watcherFirefox = watch(distDirFirefox, { recursive: true }, (eventType, filename) => {
|
||||
if (filename) {
|
||||
console.log(`[DevServer] Firefox file changed: ${filename}`);
|
||||
|
||||
// Send reload message to all connected clients
|
||||
const message = JSON.stringify({ type: "reload", browser: "firefox", file: filename });
|
||||
clients.forEach((client) => {
|
||||
if (client.readyState === 1) { // OPEN state
|
||||
client.send(message);
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Start server
|
||||
server.listen(PORT, () => {
|
||||
console.log(`[DevServer] WebSocket server running on ws://localhost:${PORT}`);
|
||||
console.log(`[DevServer] Watching for changes in ${distDirChrome} and ${distDirFirefox}`);
|
||||
});
|
||||
|
||||
// Graceful shutdown
|
||||
process.on("SIGINT", () => {
|
||||
console.log("\n[DevServer] Shutting down...");
|
||||
watcherChrome.close();
|
||||
watcherFirefox.close();
|
||||
clients.forEach((client) => client.close());
|
||||
server.close(() => {
|
||||
process.exit(0);
|
||||
});
|
||||
});
|
||||
Loading…
Add table
Add a link
Reference in a new issue