mirror of
https://github.com/getcompanion-ai/co-mono.git
synced 2026-04-16 15:02:32 +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
42
packages/browser-extension/src/utils/format.ts
Normal file
42
packages/browser-extension/src/utils/format.ts
Normal file
|
|
@ -0,0 +1,42 @@
|
|||
import { i18n } from "@mariozechner/mini-lit";
|
||||
import type { Usage } from "@mariozechner/pi-ai";
|
||||
|
||||
export function formatCost(cost: number): string {
|
||||
return `$${cost.toFixed(4)}`;
|
||||
}
|
||||
|
||||
export function formatModelCost(cost: any): string {
|
||||
if (!cost) return i18n("Free");
|
||||
const input = cost.input || 0;
|
||||
const output = cost.output || 0;
|
||||
if (input === 0 && output === 0) return i18n("Free");
|
||||
|
||||
// Format numbers with appropriate precision
|
||||
const formatNum = (num: number): string => {
|
||||
if (num >= 100) return num.toFixed(0);
|
||||
if (num >= 10) return num.toFixed(1).replace(/\.0$/, "");
|
||||
if (num >= 1) return num.toFixed(2).replace(/\.?0+$/, "");
|
||||
return num.toFixed(3).replace(/\.?0+$/, "");
|
||||
};
|
||||
|
||||
return `$${formatNum(input)}/$${formatNum(output)}`;
|
||||
}
|
||||
|
||||
export function formatUsage(usage: Usage) {
|
||||
if (!usage) return "";
|
||||
|
||||
const parts = [];
|
||||
if (usage.input) parts.push(`↑${formatTokenCount(usage.input)}`);
|
||||
if (usage.output) parts.push(`↓${formatTokenCount(usage.output)}`);
|
||||
if (usage.cacheRead) parts.push(`R${formatTokenCount(usage.cacheRead)}`);
|
||||
if (usage.cacheWrite) parts.push(`W${formatTokenCount(usage.cacheWrite)}`);
|
||||
if (usage.cost?.total) parts.push(formatCost(usage.cost.total));
|
||||
|
||||
return parts.join(" ");
|
||||
}
|
||||
|
||||
export function formatTokenCount(count: number): string {
|
||||
if (count < 1000) return count.toString();
|
||||
if (count < 10000) return (count / 1000).toFixed(1) + "k";
|
||||
return Math.round(count / 1000) + "k";
|
||||
}
|
||||
46
packages/browser-extension/src/utils/i18n.ts
Normal file
46
packages/browser-extension/src/utils/i18n.ts
Normal file
|
|
@ -0,0 +1,46 @@
|
|||
import { defaultEnglish, defaultGerman, type MiniLitRequiredMessages, setTranslations } from "@mariozechner/mini-lit";
|
||||
|
||||
declare module "@mariozechner/mini-lit" {
|
||||
interface i18nMessages extends MiniLitRequiredMessages {
|
||||
Free: string;
|
||||
"Input Required": string;
|
||||
Cancel: string;
|
||||
Confirm: string;
|
||||
"Select Model": string;
|
||||
"Search models...": string;
|
||||
Format: string;
|
||||
Thinking: string;
|
||||
Vision: string;
|
||||
}
|
||||
}
|
||||
|
||||
const translations = {
|
||||
en: {
|
||||
...defaultEnglish,
|
||||
Free: "Free",
|
||||
"Input Required": "Input Required",
|
||||
Cancel: "Cancel",
|
||||
Confirm: "Confirm",
|
||||
"Select Model": "Select Model",
|
||||
"Search models...": "Search models...",
|
||||
Format: "Format",
|
||||
Thinking: "Thinking",
|
||||
Vision: "Vision",
|
||||
},
|
||||
de: {
|
||||
...defaultGerman,
|
||||
Free: "Kostenlos",
|
||||
"Input Required": "Eingabe erforderlich",
|
||||
Cancel: "Abbrechen",
|
||||
Confirm: "Bestätigen",
|
||||
"Select Model": "Modell auswählen",
|
||||
"Search models...": "Modelle suchen...",
|
||||
Format: "Formatieren",
|
||||
Thinking: "Thinking",
|
||||
Vision: "Vision",
|
||||
},
|
||||
};
|
||||
|
||||
setTranslations(translations);
|
||||
|
||||
export * from "@mariozechner/mini-lit/dist/i18n.js";
|
||||
Loading…
Add table
Add a link
Reference in a new issue