Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@

### Dependencies and maintenance

- Delete the unreachable daemon chat pipeline and unused browser media adapters; keep chat on the agent endpoint and audio on the bounded chunked decoder.
- Use one slide-download/cache/progress workflow with source-specific download and stream-fallback policies instead of three copied pipelines.
- Return DOM documents directly instead of exposing no-op cleanup lifecycles, and share YouTube player-response parsing across captions, metadata, and page extraction.
- Centralize test output streams and browser-extension lifecycles while preserving isolated profiles, runtime-error checks, and scenario coverage.
- Remove the sidepanel's redundant action/reducer/dispatch layer; use one state object with explicit lifecycle transitions while preserving navigation, chat, and slide restoration.
Expand Down
60 changes: 0 additions & 60 deletions apps/chrome-extension/src/entrypoints/background/browser-media.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@ import { isPublicBrowserUrl } from "../../lib/browser-url-content";
import { BrowserPcmAccumulator } from "./browser-media-audio";

const MAX_BROWSER_MEDIA_BYTES = 128 * 1024 * 1024;
const MAX_BROWSER_PCM_BYTES = 512 * 1024 * 1024;
const MAX_BROWSER_PCM_CHUNK_BYTES = 64 * 1024 * 1024;
const BROWSER_AUDIO_CHUNK_SECONDS = 15 * 60;
const BROWSER_MEDIA_URL_CACHE_BYTES = 24 * 1024 * 1024;
Expand Down Expand Up @@ -208,54 +207,6 @@ export async function processBrowserAudioBytesWithMediaBunny({
return await processBrowserAudioInput({ input, onChunk });
}

export async function decodeBrowserAudioBytesWithMediaBunny({
inputBytes,
mimeType,
}: {
inputBytes: Uint8Array;
mimeType: string;
}): Promise<Float32Array> {
if (inputBytes.byteLength === 0) throw new Error("The resolved audio stream is empty.");
if (inputBytes.byteLength > MAX_BROWSER_MEDIA_BYTES) {
throw new Error("Audio is too large for in-browser decoding.");
}

const chunks: Float32Array[] = [];
let totalBytes = 0;
await processBrowserAudioBytesWithMediaBunny({
inputBytes,
mimeType,
onChunk: async ({ audio }) => {
totalBytes += audio.byteLength;
if (totalBytes > MAX_BROWSER_PCM_BYTES) {
throw new Error("Decoded audio is too long for buffered in-browser transcription.");
}
chunks.push(audio);
},
});
return concatenateFloat32(chunks);
}

export async function decodeBrowserAudioBytesWithWebAudio(
inputBytes: Uint8Array,
): Promise<Float32Array> {
if (inputBytes.byteLength === 0) throw new Error("The resolved audio stream is empty.");
if (inputBytes.byteLength > MAX_BROWSER_MEDIA_BYTES) {
throw new Error("Audio is too large for in-browser decoding.");
}

const context = new OfflineAudioContext(1, 1, TARGET_AUDIO_SAMPLE_RATE);
const decoded = await context.decodeAudioData(exactArrayBuffer(inputBytes));
const output = new Float32Array(decoded.length);
for (let channelIndex = 0; channelIndex < decoded.numberOfChannels; channelIndex += 1) {
const channel = decoded.getChannelData(channelIndex);
for (let index = 0; index < channel.length; index += 1) {
output[index] = (output[index] ?? 0) + channel[index] / decoded.numberOfChannels;
}
}
return output;
}

let creatingOffscreenDocument: Promise<void> | null = null;

export async function ensureOffscreenDocument(): Promise<void> {
Expand Down Expand Up @@ -375,17 +326,6 @@ async function processBrowserAudioInput({
}
}

function concatenateFloat32(chunks: Float32Array[]): Float32Array {
const length = chunks.reduce((sum, chunk) => sum + chunk.length, 0);
const combined = new Float32Array(length);
let offset = 0;
for (const chunk of chunks) {
combined.set(chunk, offset);
offset += chunk.length;
}
return combined;
}

export async function browserMediaCanvasToDataUrl({ canvas }: WrappedCanvas): Promise<string> {
if (!(typeof OffscreenCanvas !== "undefined" && canvas instanceof OffscreenCanvas)) {
// Chrome throttles HTMLCanvasElement.toBlob() to roughly one callback per second in offscreen documents.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -85,10 +85,6 @@ async function cropVisibleTabCapture(dataUrl: string, frame: PreparedFrame): Pro
return await blobToDataUrl(cropped);
}

export function getBrowserSlidesPayload(runId: string): SseSlidesData | null {
return browserSlidesByRunId.get(runId)?.slides ?? null;
}

export function takeBrowserSlidesPayload(runId: string): SseSlidesData | null {
const stored = browserSlidesByRunId.get(runId);
if (!stored || stored.expiresAt <= Date.now()) return null;
Expand Down
2 changes: 2 additions & 0 deletions docs/agent.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,8 @@ Explicit exclusions (per product direction): **no update checker**, **no tutoria
3. **Content scripts** handle element picking and native-input bridge.
4. **Daemon** provides `/v1/agent` (SSE stream of chunks + final assistant message).

Chat-only Q&A and automation use this same endpoint and implementation. The automation flag selects the prompt and available tools; there is no separate chat execution pipeline.

### Data Flow (Agent Loop)

- User sends a message in the side panel.
Expand Down
202 changes: 0 additions & 202 deletions src/daemon/chat.ts

This file was deleted.

10 changes: 1 addition & 9 deletions src/slides/extract.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,7 @@ import { resolveBundledFfmpegCommand } from "@steipete/summarize-core/ffmpeg";
import { resolveExecutableInPath } from "../application/environment.js";
import type { MediaCache } from "../content/index.js";
import { canSpawnCommand } from "../run/env.js";
import {
buildSlidesMediaCacheKey,
downloadRemoteVideo,
downloadYoutubeVideo,
formatBytes,
resolveYoutubeStreamUrl,
} from "./download.js";
import { downloadRemoteVideo, downloadYoutubeVideo, resolveYoutubeStreamUrl } from "./download.js";
import {
buildSlideTimeline,
buildSlidesChunkMeta,
Expand Down Expand Up @@ -310,8 +304,6 @@ export async function extractSlidesForSource({
ytDlpCookiesFromBrowser,
resolveSlidesYtDlpExtractFormat: () => resolveSlidesYtDlpExtractFormat(env),
resolveSlidesStreamFallback: () => resolveSlidesStreamFallback(env),
buildSlidesMediaCacheKey,
formatBytes,
reportSlidesProgress,
logSlidesTiming,
downloadYoutubeVideo,
Expand Down
Loading