mirror of
https://github.com/Heretek-AI/ompweb.git
synced 2026-11-07 10:56:00 -10:00
* feat(dictation): recording deck with timer, waveform, pause, retry and send modes * fix(dictation): remove stray pulse ring and let waveform shrink on narrow composers * test(systemd): scrub ambient OMP_WEB_OMP_BIN so install test is hermetic * feat(dictation): mic becomes cancel while recording, composer Send transcribes+queues, deck keeps only pause and stop * fix(dictation): apply review findings — theme tokens, audio lifecycle, attachment staleness, dynamic waveform width - Use the theme's --status-error token instead of undefined --danger custom properties so dictation colors follow light/dark themes. - Close the AudioContext and release the analyser when capture finishes, not only on cleanup, so transcription no longer holds the mic graph. - Read attachments through refs inside handleSend/sendQueued so files attached mid-recording are included when the transcript dispatches. - Surface 'No speech detected' when a capture yields no audio instead of failing silently. - Stretch the waveform canvas to the free composer width (dynamic bar count, newest samples at the buttons) and freeze it gray while paused. - Make the transcribe-and-send wiring test assert the contiguous branch. - Drop the orphaned chatInput.stopDictation i18n key. * fix(dictation): address coderabbit findings — audio context ownership, state-gated finish, enter passthrough * feat(dictation): play recording before transcribe Stop enters review with playback instead of sending immediately. Paused capture keeps a left-side preview play button. * feat(dictation): fix audio playback lifecycle, timeslice, and replay reset * fix(dictation): address review findings (timer corruption, audio leak, icon, test honesty) * fix(dictation): restore setIsRecording(true) on capture start and add behavioral test suite * fix(dictation): allow blob: media in CSP so preview playback works; mic icon for resume * fix(dictation): draw recorded bars and playback progress in review-mode waveform * fix(dictation): stop replaced preview audio; neutral confirm label * chore(ci): retrigger windows verify after flaky MessageView copy test
117 lines
5.5 KiB
TypeScript
117 lines
5.5 KiB
TypeScript
import type { NextConfig } from "next";
|
|
import { readFileSync } from "fs";
|
|
import { join } from "path";
|
|
import { PHASE_DEVELOPMENT_SERVER } from "next/constants";
|
|
|
|
const { version } = JSON.parse(readFileSync(join(__dirname, "package.json"), "utf8")) as { version: string };
|
|
|
|
// Function form: `phase` is authoritative even when the host environment
|
|
// carries a stray NODE_ENV (e.g. NODE_ENV=production inherited by `next dev`
|
|
// processes). Relying on process.env.NODE_ENV here would apply the immutable
|
|
// chunk rule in development, make the browser cache dev chunks forever, and
|
|
// produce stale "module factory is not available" / hydration errors after
|
|
// every restart.
|
|
const nextConfig = (phase: string): NextConfig => {
|
|
const isDev = phase === PHASE_DEVELOPMENT_SERVER;
|
|
return {
|
|
// Keep standalone/server tracing inside this package. Without this explicit
|
|
// root, Next can choose a parent lockfile on Windows and traverse protected
|
|
// user-profile junctions while compiling.
|
|
outputFileTracingRoot: process.cwd(),
|
|
// undici is loaded from a runtime dependency (lib/http-dispatcher.ts) to
|
|
// honor HTTP(S)_PROXY for server-side fetch; keep it external so the
|
|
// bundler does not inline a second copy next to the global dispatcher.
|
|
serverExternalPackages: ["undici"],
|
|
webpack(config: Parameters<NonNullable<NextConfig["webpack"]>>[0]) {
|
|
// Next's entrypoint tracer does not automatically reject dynamic paths
|
|
// outside the project root. Add parent/profile patterns to its ignore list
|
|
// so user filesystem discovery remains request-time only during builds.
|
|
for (const plugin of config.plugins ?? []) {
|
|
const candidate = plugin as unknown as {
|
|
constructor?: { name?: string };
|
|
traceIgnores?: string[];
|
|
};
|
|
if (candidate.constructor?.name === "TraceEntryPointsPlugin") {
|
|
candidate.traceIgnores ??= [];
|
|
candidate.traceIgnores.push(
|
|
"**/../**",
|
|
"**/Users/**",
|
|
"**/Application Data/**",
|
|
"**/omp-web-bundled-agents/**",
|
|
);
|
|
}
|
|
}
|
|
return config;
|
|
},
|
|
allowedDevOrigins: ["127.0.0.1", "192.168.*.*"],
|
|
// Security: stop advertising the runtime, and surface dev-mode problems
|
|
// earlier. Source maps in the browser bundle leak server path layout and
|
|
// bloat downloads without helping end users of a published app.
|
|
poweredByHeader: false,
|
|
reactStrictMode: true,
|
|
productionBrowserSourceMaps: false,
|
|
// Next.js enables gzip/brotli compression for `next start` by default; no
|
|
// custom compression middleware is needed (and would require a custom server).
|
|
async headers() {
|
|
const securityHeaders = [
|
|
{ key: "X-Content-Type-Options", value: "nosniff" },
|
|
{ key: "X-Frame-Options", value: "DENY" },
|
|
{ key: "Referrer-Policy", value: "no-referrer" },
|
|
{ key: "Permissions-Policy", value: "camera=(), microphone=(self), geolocation=()" },
|
|
{ key: "Content-Security-Policy", value: "default-src 'self'; base-uri 'self'; form-action 'self'; frame-ancestors 'none'; object-src 'none'; img-src 'self' data: blob: https:; style-src 'self' 'unsafe-inline'; script-src 'self' 'unsafe-inline' 'unsafe-eval'; connect-src 'self' ws: wss:; font-src 'self' data:; media-src 'self' blob:" },
|
|
];
|
|
// /api/files streams workspace files whose document policy depends on the
|
|
// content type (strict CSP for SVG, the DOCX preview policy, none for
|
|
// media the browser renders natively). Config-level headers overwrite
|
|
// same-key headers set by route handlers, so the global CSP must not
|
|
// match these paths — it would both reopen script execution for SVG
|
|
// documents and block the same-origin <iframe> previews (DOCX/PDF) with
|
|
// frame-ancestors 'none'. The handler-agnostic protections stay here.
|
|
const fileResponseHeaders = [
|
|
{ key: "X-Content-Type-Options", value: "nosniff" },
|
|
{ key: "X-Frame-Options", value: "SAMEORIGIN" },
|
|
{ key: "Referrer-Policy", value: "no-referrer" },
|
|
{ key: "Permissions-Policy", value: "camera=(), microphone=(), geolocation=()" },
|
|
];
|
|
const globalRule = {
|
|
// Everything except /api/files (negative lookahead, same pattern style
|
|
// as the proxy matcher).
|
|
source: "/((?!api/files/).*)",
|
|
headers: securityHeaders,
|
|
};
|
|
const fileRule = {
|
|
source: "/api/files/:path*",
|
|
headers: fileResponseHeaders,
|
|
};
|
|
const rootNoCacheRule = {
|
|
source: "/",
|
|
headers: [
|
|
{ key: "Cache-Control", value: "private, no-cache, max-age=0, must-revalidate" },
|
|
],
|
|
};
|
|
const staticImmutableRule = {
|
|
// Hashed build output never changes, so browsers/proxies may cache it
|
|
// immutably for a year and skip revalidation entirely.
|
|
// NOTE: scoped to /_next/static/ only — broader /_next/ patterns would
|
|
// shadow the HMR WebSocket in development.
|
|
source: "/_next/static/:path*",
|
|
headers: [
|
|
{ key: "Cache-Control", value: "public, max-age=31536000, immutable" },
|
|
],
|
|
};
|
|
|
|
// Dev chunks have stable URLs whose content changes in place; caching
|
|
// them immutably would serve stale module factories after a restart.
|
|
if (isDev) return [globalRule, fileRule, rootNoCacheRule];
|
|
|
|
return [globalRule, fileRule, staticImmutableRule, rootNoCacheRule];
|
|
},
|
|
env: {
|
|
NEXT_PUBLIC_APP_VERSION: version,
|
|
NEXT_PUBLIC_OMP_WEB_VERSION: version,
|
|
},
|
|
};
|
|
};
|
|
|
|
export default nextConfig;
|