refactor(frontend): isolate audio meters and apply settings on save

This commit is contained in:
2026-10-07 09:29:20 -03:00
parent 09bf6ece00
commit 0c57f7c76d
7 changed files with 240 additions and 105 deletions
+39 -8
View File
@@ -53,7 +53,6 @@ export interface AppState {
notice: string;
messages: Message[];
muted: boolean;
level: number;
rag: string;
queuePosition: number;
joinDeadline: number;
@@ -65,6 +64,8 @@ export class VoiceController {
readonly caller: Caller;
private state: AppState;
private readonly listeners = new Set<() => void>();
private readonly levelListeners = new Set<() => void>();
private level = 0;
private client: RealtimeClient | null = null;
private subscriptions: (() => void)[] = [];
private requests = new AbortController();
@@ -98,7 +99,6 @@ export class VoiceController {
notice: "",
messages: [],
muted: false,
level: 0,
rag: "",
queuePosition: 0,
joinDeadline: 0,
@@ -106,6 +106,22 @@ export class VoiceController {
};
}
getSnapshot = () => this.state;
getLevel = () => this.level;
subscribeLevel = (listener: () => void) => {
this.levelListeners.add(listener);
return () => {
this.levelListeners.delete(listener);
};
};
private updateLevel(level: number) {
if (this.level === level) {
return;
}
this.level = level;
this.levelListeners.forEach((listener) => {
listener();
});
}
subscribe = (listener: () => void) => {
this.listeners.add(listener);
return () => {
@@ -215,13 +231,28 @@ export class VoiceController {
this.state.settings.voice,
this.tools(),
);
this.client?.setGate(this.gate());
}
changeSettings(patch: Partial<Settings>) {
const settings = { ...this.state.settings, ...patch };
const changed = Object.fromEntries(
Object.entries(patch).filter(([key, value]) => this.state.settings[key as keyof Settings] !== value),
) as Partial<Settings>;
if (!Object.keys(changed).length) {
return;
}
const settings = { ...this.state.settings, ...changed };
this.update({ settings });
saveSettings(settings);
this.pushSettings();
saveSettings(changed);
if (changed.noiseGate !== undefined) {
this.client?.setGate(this.gate());
}
if (
changed.voice !== undefined ||
changed.instructions !== undefined ||
changed.webSearch !== undefined ||
changed.searchKey !== undefined
) {
this.pushSettings();
}
}
setRag(rag: string) {
this.update({ rag: rag.trim() });
@@ -338,7 +369,7 @@ export class VoiceController {
}
if (Date.now() - this.lastMeter > 150) {
this.lastMeter = Date.now();
this.update({ level });
this.updateLevel(level);
}
}),
client.on("queue", ({ position }) => {
@@ -574,10 +605,10 @@ export class VoiceController {
this.requests.abort();
this.cancelReasoning();
window.speechSynthesis?.cancel();
this.updateLevel(0);
this.update({
status,
muted: false,
level: 0,
joinDeadline: 0,
unlock: false,
messages: this.state.messages.map((message) =>