Implement audio visualizers and refactor AssistantPage

- Introduce three new audio visualizer components: AuraVisualizer, SpectrumVisualizer, and WaveVisualizer, enhancing the audio interaction experience.
- Replace the deprecated VoiceVisualizer with the new visualizers, ensuring a cohesive visual language across components.
- Update the AssistantPage to support dynamic visualization style switching, improving user engagement during audio interactions.
- Refactor DebugVoicePanel to accommodate the new visualizer props and enhance the overall debugging interface.
This commit is contained in:
Xin Wang
2026-06-09 16:28:45 +08:00
parent 4f0f639e8f
commit b3fbfac5df
7 changed files with 813 additions and 397 deletions

View File

@@ -0,0 +1,62 @@
// 可视化共用的配色工具:从设计 token--gradient-*)读取调色板,
// 让三种可视化样式自动跟随明暗主题,并保持同一套视觉语言。
export type RGB = { r: number; g: number; b: number };
export type Palette = { sky: RGB; lav: RGB; rose: RGB };
/** 浅色主题下的回退值,避免首帧 token 未就绪时闪烁 */
const FALLBACK: Palette = {
sky: { r: 168, g: 200, b: 232 },
lav: { r: 200, g: 184, b: 224 },
rose: { r: 232, g: 184, b: 196 },
};
export function parseHex(hex: string, fallback: RGB): RGB {
const m = hex.trim().replace("#", "");
if (m.length === 6) {
return {
r: parseInt(m.slice(0, 2), 16),
g: parseInt(m.slice(2, 4), 16),
b: parseInt(m.slice(4, 6), 16),
};
}
if (m.length === 3) {
return {
r: parseInt(m[0] + m[0], 16),
g: parseInt(m[1] + m[1], 16),
b: parseInt(m[2] + m[2], 16),
};
}
return fallback;
}
export function rgba({ r, g, b }: RGB, a: number) {
return `rgba(${r}, ${g}, ${b}, ${a})`;
}
export function mix(a: RGB, b: RGB, t: number): RGB {
return {
r: Math.round(a.r + (b.r - a.r) * t),
g: Math.round(a.g + (b.g - a.g) * t),
b: Math.round(a.b + (b.b - a.b) * t),
};
}
/** 在 sky → lavender → rose 三色之间取环形插值(首尾相连,适合沿圆周/横向铺色) */
export function cyclicColor({ sky, lav, rose }: Palette, p: number): RGB {
const stops = [sky, lav, rose];
const scaled = ((p % 1) + 1) % 1 * stops.length;
const i = Math.floor(scaled);
return mix(stops[i % stops.length], stops[(i + 1) % stops.length], scaled - i);
}
/** 从元素上读取当前主题的 --gradient-* token返回 0~255 的 RGB */
export function readPalette(el: Element): Palette {
const s = getComputedStyle(el);
return {
sky: parseHex(s.getPropertyValue("--gradient-sky"), FALLBACK.sky),
lav: parseHex(s.getPropertyValue("--gradient-lavender"), FALLBACK.lav),
rose: parseHex(s.getPropertyValue("--gradient-rose"), FALLBACK.rose),
};
}