From b06f20f9d88f0577b81bf060c1daab25085f28d3 Mon Sep 17 00:00:00 2001 From: hobokenchicken Date: Sat, 6 Jun 2026 12:05:28 -0400 Subject: [PATCH] chore: cleanup dead code and unused files Deleted 7 unused frontend components (ChatBubble, PetZone, Live2DCat, BackgroundScene, Clock, Toolbar, types/index.ts). Deleted unused backend: whisper_stream.py, empty models/ and routers/ dirs. Removed dead code: Message interface, messages/micError/sendText/addMessage, conversation_text handler, 3 unused memory.py methods. Fixed task persistence bug: added 'tasks' to DEFAULT_PREFERENCES. Stale comment in Live2DStage updated. --- backend/main.py | 23 --- backend/models/__init__.py | 0 backend/routers/__init__.py | 0 backend/services/memory.py | 47 +----- backend/services/whisper_stream.py | 158 -------------------- frontend/src/App.tsx | 1 - frontend/src/components/BackgroundScene.tsx | 35 ----- frontend/src/components/ChatBubble.tsx | 85 ----------- frontend/src/components/Clock.tsx | 21 --- frontend/src/components/Live2DCat.tsx | 76 ---------- frontend/src/components/Live2DStage.tsx | 2 +- frontend/src/components/PetZone.tsx | 12 -- frontend/src/components/Toolbar.tsx | 34 ----- frontend/src/hooks/useConversation.ts | 23 +-- frontend/src/index.css | 9 -- frontend/src/types/index.ts | 13 -- frontend/tsconfig.tsbuildinfo | 2 +- 17 files changed, 4 insertions(+), 537 deletions(-) delete mode 100644 backend/models/__init__.py delete mode 100644 backend/routers/__init__.py delete mode 100644 backend/services/whisper_stream.py delete mode 100644 frontend/src/components/BackgroundScene.tsx delete mode 100644 frontend/src/components/ChatBubble.tsx delete mode 100644 frontend/src/components/Clock.tsx delete mode 100644 frontend/src/components/Live2DCat.tsx delete mode 100644 frontend/src/components/PetZone.tsx delete mode 100644 frontend/src/components/Toolbar.tsx delete mode 100644 frontend/src/types/index.ts diff --git a/backend/main.py b/backend/main.py index 8509f5a..82e4003 100644 --- a/backend/main.py +++ b/backend/main.py @@ -383,29 +383,6 @@ async def gemini_voice_ws(websocket: WebSocket): await gemini_ws.send(json.dumps(gemini_msg)) continue - if msg_type == "conversation_text": - text = msg.get("text", "").strip() - if not text: - continue - logger.info(f"[{session_id}] User (text): {text}") - if gemini_ws and gemini_ws.state.name == "OPEN": - user_part = {"text": text} - if memory_suffix: - user_part = {"text": f"[Context: {memory_suffix}]\n{text}"} - gemini_msg = { - "clientContent": { - "turns": [{"role": "user", "parts": [user_part]}], - "turnComplete": True, - } - } - await gemini_ws.send(json.dumps(gemini_msg)) - await websocket.send_json({ - "type": "transcript", - "role": "user", - "text": text, - }) - continue - if msg_type == "ping": await websocket.send_json({"type": "pong"}) diff --git a/backend/models/__init__.py b/backend/models/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/backend/routers/__init__.py b/backend/routers/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/backend/services/memory.py b/backend/services/memory.py index 2377fd2..2b0af04 100644 --- a/backend/services/memory.py +++ b/backend/services/memory.py @@ -134,57 +134,12 @@ class KiraMemory: return "\n\n---\n### What Kira remembers:" + "".join(parts) - def store_messages( - self, - user_message: str, - kira_message: str, - ) -> None: - """Store a conversation exchange in Honcho.""" - if not self.enabled or not self._session: - return - - try: - messages = [] - if self._user_peer: - messages.append( - self._user_peer.message(user_message) - ) - if self._kira_peer: - messages.append( - self._kira_peer.message(kira_message) - ) - - if messages: - self._session.add_messages(messages) - logger.debug("Stored conversation exchange in Honcho") - except Exception as e: - logger.warning(f"Failed to store messages: {e}") - - def store_user_message(self, text: str) -> None: - """Store a single user message.""" - if not self.enabled or not self._session or not self._user_peer: - return - try: - self._session.add_messages([self._user_peer.message(text)]) - except Exception as e: - logger.warning(f"Failed to store user message: {e}") - - def store_kira_message(self, text: str) -> None: - """Store a single Kira message.""" - if not self.enabled or not self._session or not self._kira_peer: - return - try: - self._session.add_messages([self._kira_peer.message(text)]) - except Exception as e: - logger.warning(f"Failed to store Kira message: {e}") - - # ─── User preferences (stored in Honcho peer metadata) ─── - DEFAULT_PREFERENCES: dict[str, str] = { "name": "", "scene": "cozy-room", "outfit": "cozy-hoodie", "accessory": "", + "tasks": "[]", } def get_user_preferences(self, user_id: str) -> dict[str, str]: diff --git a/backend/services/whisper_stream.py b/backend/services/whisper_stream.py deleted file mode 100644 index 94788b9..0000000 --- a/backend/services/whisper_stream.py +++ /dev/null @@ -1,158 +0,0 @@ -"""Realtime streaming transcription service via gpt-realtime-whisper. - -Connects to OpenAI Realtime API via WebSocket, configures the session -for pure transcription (no model responses), and streams word-level -transcript deltas back. Full utterances are then processed by the -cheap LLM + TTS pipeline. -""" - -import json -import base64 -import logging -import asyncio -from typing import Callable, Awaitable -from config import settings - -logger = logging.getLogger("kira.whisper") - - -class WhisperStream: - """Streaming transcription via gpt-realtime-whisper over WebSocket.""" - - def __init__( - self, - on_transcript_delta: Callable[[str], Awaitable[None]], - on_transcript_done: Callable[[str], Awaitable[None]], - on_ready: Callable[[], Awaitable[None]], - on_error: Callable[[str], Awaitable[None]], - ): - self._on_delta = on_transcript_delta - self._on_done = on_transcript_done - self._on_ready = on_ready - self._on_error = on_error - self._conn = None - self._connected = False - self._transcript = "" - - async def connect(self): - if self._connected: - return - - try: - import websockets - - url = "wss://api.openai.com/v1/realtime?model=gpt-4o-realtime-preview" - ws = await websockets.connect( - url, - additional_headers={ - "Authorization": f"Bearer {settings.openai_api_key}", - }, - ) - - async with ws as conn: - self._conn = conn - self._connected = True - logger.info("Connected to Realtime transcription session") - - # Configure: transcribe only with gpt-realtime-whisper, no model responses - await self._send({ - "type": "session.update", - "session": { - "input_audio_format": "pcm16", - "input_audio_transcription": { - "model": "gpt-realtime-whisper", - "enabled": True, - }, - "turn_detection": { - "type": "server_vad", - "threshold": 0.5, - "prefix_padding_ms": 300, - "silence_duration_ms": 600, - }, - }, - }) - - await self._on_ready() - - while self._connected: - try: - raw = await conn.recv() - if isinstance(raw, (str, bytes)): - data = json.loads(raw if isinstance(raw, str) else raw.decode()) - await self._handle(data) - except Exception as e: - if self._connected: - logger.warning(f"recv: {e}") - break - - except Exception as e: - logger.error(f"Whisper stream error: {e}") - await self._on_error(str(e)) - finally: - self._connected = False - self._conn = None - - async def _handle(self, data: dict): - et = data.get("type", "") - - if et == "input_audio_buffer.speech_started": - self._transcript = "" - logger.debug("speech_started") - - elif et in ("conversation.item.input_audio_transcription.delta", "input_audio_buffer.transcription.delta"): - # Partial streaming transcript - delta = data.get("delta", "") or data.get("transcript", "") - if delta: - self._transcript = delta # or append if cumulative - await self._on_delta(delta) - - elif et in ("conversation.item.input_audio_transcription.completed", "input_audio_buffer.transcription.completed", "conversation.item.created"): - # Final or item created with transcript - item = data.get("item", {}) - content = item.get("content", []) if item else [] - transcript = data.get("transcript", "") - if not transcript: - for part in (content or []): - if part.get("type") in ("transcript", "text"): - transcript = part.get("transcript", "") or part.get("text", "") - break - if transcript: - self._transcript = transcript - await self._on_delta(transcript) - await self._on_done(transcript.strip()) - self._transcript = "" - - elif et == "input_audio_buffer.speech_stopped": - if self._transcript.strip(): - await self._on_done(self._transcript.strip()) - self._transcript = "" - - elif et == "error": - err = data.get("error", {}) - msg = err.get("message", str(data)) - logger.warning(f"Whisper error: {msg}") - await self._on_error(msg) - - async def send_audio(self, pcm16_bytes: bytes): - if not self._connected: - return - try: - b64 = base64.b64encode(pcm16_bytes).decode("utf-8") - await self._send({"type": "input_audio_buffer.append", "audio": b64}) - except Exception as e: - logger.warning(f"send audio: {e}") - - async def _send(self, data: dict): - try: - await self._conn.send(json.dumps(data)) - except Exception as e: - logger.warning(f"send: {e}") - - async def disconnect(self): - self._connected = False - if self._conn: - try: - await self._conn.close() - except Exception: - pass - self._conn = None diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx index 1e8d34a..26dfb78 100644 --- a/frontend/src/App.tsx +++ b/frontend/src/App.tsx @@ -16,7 +16,6 @@ import { useConversation } from './hooks/useConversation'; export default function App() { const [isLoggedIn, setIsLoggedIn] = useState(() => sessionStorage.getItem('kira-auth') === '1'); const { - messages, isConnected, isKiraSpeaking, isRecording, diff --git a/frontend/src/components/BackgroundScene.tsx b/frontend/src/components/BackgroundScene.tsx deleted file mode 100644 index f97f552..0000000 --- a/frontend/src/components/BackgroundScene.tsx +++ /dev/null @@ -1,35 +0,0 @@ -import { SCENES } from './scenes'; - -interface Props { - currentScene: string; - onSelect: (id: string) => void; -} - -export default function BackgroundScene({ currentScene, onSelect }: Props) { - return ( -
-

- 🎨 Scene -

-
- {SCENES.map((s) => ( - - ))} -
-
- ); -} diff --git a/frontend/src/components/ChatBubble.tsx b/frontend/src/components/ChatBubble.tsx deleted file mode 100644 index affe0bd..0000000 --- a/frontend/src/components/ChatBubble.tsx +++ /dev/null @@ -1,85 +0,0 @@ -import { useRef, useEffect } from 'react'; - -interface Message { - id: string; - role: 'user' | 'kira'; - text: string; - timestamp: number; -} - -interface Props { - messages: Message[]; - isKiraSpeaking: boolean; - userName?: string; -} - -export default function ChatBubble({ messages, isKiraSpeaking }: Props) { - const bottomRef = useRef(null); - - useEffect(() => { - bottomRef.current?.scrollIntoView({ behavior: 'smooth' }); - }, [messages]); - - return ( -
-

- 💬 Conversation - -

- -
- {messages.length === 0 && ( -
- click the mic or type to talk to Kira -
- )} - - {messages.map((msg, i) => { - const isLastKira = msg.role === 'kira' && i === messages.length - 1 && isKiraSpeaking; - return ( -
- {msg.role === 'kira' && ( - 🌸 - )} -
- {msg.text} - {isLastKira && ( - - )} -
- {msg.role === 'user' && ( - 👤 - )} -
- ); - })} -
-
- - -
- ); -} diff --git a/frontend/src/components/Clock.tsx b/frontend/src/components/Clock.tsx deleted file mode 100644 index 923ed26..0000000 --- a/frontend/src/components/Clock.tsx +++ /dev/null @@ -1,21 +0,0 @@ -import { useEffect, useState } from 'react'; - -export default function Clock() { - const [time, setTime] = useState(new Date()); - - useEffect(() => { - const id = setInterval(() => setTime(new Date()), 1000); - return () => clearInterval(id); - }, []); - - return ( -
-
- {time.toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' })} -
-
- {time.toLocaleDateString([], { weekday: 'long', month: 'long', day: 'numeric' })} -
-
- ); -} diff --git a/frontend/src/components/Live2DCat.tsx b/frontend/src/components/Live2DCat.tsx deleted file mode 100644 index ab4c50d..0000000 --- a/frontend/src/components/Live2DCat.tsx +++ /dev/null @@ -1,76 +0,0 @@ -import { useEffect, useRef } from 'react'; - -interface Props { - className?: string; -} - -export default function Live2DCat({ className }: Props) { - const canvasRef = useRef(null); - - useEffect(() => { - let mounted = true; - const canvas = canvasRef.current; - if (!canvas) return; - - let app: any = null; - - const init = async () => { - try { - const pixi = await import('pixi.js'); - const { Live2DModel } = await import('pixi-live2d-display/cubism4'); - (Live2DModel as any).registerTicker(pixi.Ticker as any); - - const w = canvas.clientWidth || 120; - const h = canvas.clientHeight || 120; - - // Use explicit CanvasRenderer to avoid WebGL context conflict with KiraAvatar - app = new pixi.Application({ - view: canvas, - width: w, - height: h, - antialias: true, - resolution: Math.min(window.devicePixelRatio || 1, 2), - backgroundAlpha: 0, - autoDensity: true, - // Use WebGL1 context (more compatible with separate contexts) - preferWebGLVersion: 1, - } as any); - if (!mounted) { app.destroy(true); return; } - - const model = await Live2DModel.from('/live2d/models/little-cat/LittleCat.model3.json', { - autoInteract: false, - }); - if (!mounted) { app.destroy(true); return; } - - const sw = app.screen.width; - const sh = app.screen.height; - const s = Math.min((sw * 0.85) / model.width, (sh * 0.85) / model.height); - model.scale.set(s); - model.anchor.set(0.5, 0.5); - model.position.set(sw / 2, sh / 2); - - app.stage.addChild(model as any); - (model as any).isInteractive = () => false; - - try { model.motion('Idle'); } catch { /* */ } - } catch (e) { - console.warn('[Live2DCat]', e); - } - }; - - init(); - - return () => { - mounted = false; - if (app) { app.destroy(true, { children: true }); } - }; - }, []); - - return ( - - ); -} diff --git a/frontend/src/components/Live2DStage.tsx b/frontend/src/components/Live2DStage.tsx index c9e4c67..c856e88 100644 --- a/frontend/src/components/Live2DStage.tsx +++ b/frontend/src/components/Live2DStage.tsx @@ -11,7 +11,7 @@ interface Props { /** * Single full-viewport Live2D stage. One WebGL context, two models: * - Kira (center panel) - * - Mochi the cat (PetZone, bottom of right sidebar) + // Mochi the cat (next to Kira in center panel) * * Canvas sits above UI panels (z-50, pointer-events: none) so Live2D * models render on top of the frosted-glass sidebars. diff --git a/frontend/src/components/PetZone.tsx b/frontend/src/components/PetZone.tsx deleted file mode 100644 index e1dd6eb..0000000 --- a/frontend/src/components/PetZone.tsx +++ /dev/null @@ -1,12 +0,0 @@ -export default function PetZone() { - return ( -
-

- 🐱 Pet Zone -

-
- Mochi -
-
- ); -} diff --git a/frontend/src/components/Toolbar.tsx b/frontend/src/components/Toolbar.tsx deleted file mode 100644 index 3cef03e..0000000 --- a/frontend/src/components/Toolbar.tsx +++ /dev/null @@ -1,34 +0,0 @@ -import { SCENES } from './scenes'; - -interface Props { - currentScene: string; - onSceneChange: (id: string) => void; -} - -export default function Toolbar({ currentScene, onSceneChange }: Props) { - return ( -
-
- Kira - | -
- {SCENES.filter((_, i) => i < 4).map((s) => ( - - ))} -
-
- -
- 🐱 2 - focus bestie -
-
- ); -} diff --git a/frontend/src/hooks/useConversation.ts b/frontend/src/hooks/useConversation.ts index 7126f0c..2cbedb8 100644 --- a/frontend/src/hooks/useConversation.ts +++ b/frontend/src/hooks/useConversation.ts @@ -13,12 +13,6 @@ export interface Task { completed: boolean; } -interface Message { - id: string; - role: 'user' | 'kira'; - text: string; - timestamp: number; -} const WS_URL = `${location.protocol === 'https:' ? 'wss:' : 'ws:'}//${location.host}/api/ws`; const USER_ID_KEY = 'kira-user-id'; @@ -56,7 +50,6 @@ function float32ToPcm16Base64(float32: Float32Array, srcRate: number): string { } export function useConversation() { - const [messages, setMessages] = useState([]); const [isConnected, setIsConnected] = useState(false); const [isKiraSpeaking, setIsKiraSpeaking] = useState(false); const [isRecording, setIsRecording] = useState(false); @@ -68,7 +61,6 @@ export function useConversation() { accessory: '', }); const [loadingPrefs, setLoadingPrefs] = useState(true); - const [micError, setMicError] = useState(null); const [tasks, setTasks] = useState([]); const [celebrateTrigger, setCelebrateTrigger] = useState(0); @@ -130,7 +122,6 @@ export function useConversation() { break; case 'transcript': - addMessage(msg.role === 'user' ? 'user' : 'kira', msg.text); break; case 'audio': { @@ -220,12 +211,6 @@ export function useConversation() { return playbackCtxRef.current; } - const addMessage = useCallback((role: 'user' | 'kira', text: string) => { - setMessages((prev) => [ - ...prev, - { id: crypto.randomUUID(), role, text, timestamp: Date.now() }, - ]); - }, []); // ── Identity ── const identify = useCallback((name: string) => { @@ -248,12 +233,10 @@ export function useConversation() { // ── Audio capture via ScriptProcessorNode (PCM16 16kHz stream) ── const startRecording = useCallback(async () => { if (!navigator.mediaDevices?.getUserMedia) { - addMessage('kira', 'Mic requires HTTPS. Try accessing via HTTPS!'); return; } try { - setMicError(null); const stream = await navigator.mediaDevices.getUserMedia({ audio: { echoCancellation: true, noiseSuppression: true, sampleRate: 48000 }, }); @@ -261,7 +244,6 @@ export function useConversation() { const ws = wsRef.current; if (!ws || ws.readyState !== WebSocket.OPEN) { - addMessage('kira', 'Not connected to server yet...'); stream.getTracks().forEach((t) => t.stop()); return; } @@ -295,10 +277,9 @@ export function useConversation() { setIsRecording(true); } catch (err) { const msg = err instanceof Error ? err.message : String(err); - setMicError(msg); console.error('[Kira Mic]', msg); } - }, [addMessage]); + }, []); const stopRecording = useCallback(() => { processorRef.current?.disconnect(); @@ -328,14 +309,12 @@ export function useConversation() { }, [connect, stopRecording]); return { - messages, isConnected, isKiraSpeaking, isRecording, identified, preferences, loadingPrefs, - micError, identify, setPreference, sendText, diff --git a/frontend/src/index.css b/frontend/src/index.css index a27e389..d0ef443 100644 --- a/frontend/src/index.css +++ b/frontend/src/index.css @@ -7,7 +7,6 @@ --color-kira-bg: #FFF5F5; --color-kira-plum: #4A1942; --color-kira-violet: #7C3AED; - --color-kira-card: #FFFFFF; --color-kira-glow: #FDF2F8; --font-nunito: 'Nunito', sans-serif; } @@ -27,14 +26,6 @@ body { width: 100vw; } -.glass-card { - background: rgba(255, 255, 255, 0.7); - backdrop-filter: blur(12px); - border: 1px solid rgba(255, 182, 193, 0.3); - border-radius: 16px; - box-shadow: 0 4px 24px rgba(74, 25, 66, 0.08); -} - .btn-kira { background: linear-gradient(135deg, #FFB6C1, #D8B4FE); color: white; diff --git a/frontend/src/types/index.ts b/frontend/src/types/index.ts deleted file mode 100644 index 3fb762e..0000000 --- a/frontend/src/types/index.ts +++ /dev/null @@ -1,13 +0,0 @@ -import { SCENES, type Scene } from '../components/scenes'; - -export interface KiraState { - currentScene: Scene; - isListening: boolean; - isSpeaking: boolean; - currentOutfit: string; - currentAccessory: string | null; - sessionNotes: string[]; -} - -export type { Scene }; -export { SCENES }; diff --git a/frontend/tsconfig.tsbuildinfo b/frontend/tsconfig.tsbuildinfo index 781de6f..9972967 100644 --- a/frontend/tsconfig.tsbuildinfo +++ b/frontend/tsconfig.tsbuildinfo @@ -1 +1 @@ -{"root":["./src/App.tsx","./src/main.tsx","./src/vite-env.d.ts","./src/components/AnimatedAvatar.tsx","./src/components/BackgroundScene.tsx","./src/components/ChatBubble.tsx","./src/components/Clock.tsx","./src/components/KiraAvatar.tsx","./src/components/Live2DCat.tsx","./src/components/Live2DStage.tsx","./src/components/LoginScreen.tsx","./src/components/MusicPlayer.tsx","./src/components/Notes.tsx","./src/components/Particles.tsx","./src/components/PetZone.tsx","./src/components/TaskList.tsx","./src/components/Timer.tsx","./src/components/Toolbar.tsx","./src/components/Wardrobe.tsx","./src/components/WelcomeScreen.tsx","./src/components/WhiteNoise.tsx","./src/components/scenes.ts","./src/hooks/useConversation.ts","./src/types/index.ts"],"version":"6.0.3"} \ No newline at end of file +{"root":["./src/App.tsx","./src/main.tsx","./src/vite-env.d.ts","./src/components/AnimatedAvatar.tsx","./src/components/KiraAvatar.tsx","./src/components/Live2DStage.tsx","./src/components/LoginScreen.tsx","./src/components/MusicPlayer.tsx","./src/components/Notes.tsx","./src/components/Particles.tsx","./src/components/TaskList.tsx","./src/components/Timer.tsx","./src/components/Wardrobe.tsx","./src/components/WelcomeScreen.tsx","./src/components/WhiteNoise.tsx","./src/components/scenes.ts","./src/hooks/useConversation.ts"],"version":"6.0.3"} \ No newline at end of file