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:
62
frontend/src/lib/visualizer-palette.ts
Normal file
62
frontend/src/lib/visualizer-palette.ts
Normal 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),
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user