From b097d58f13aab262f584763bc0f409969f48a3e9 Mon Sep 17 00:00:00 2001 From: hobokenchicken Date: Sat, 6 Jun 2026 00:06:16 -0400 Subject: [PATCH] feat(tasks): text input + Honcho persistence Manual task input box in TaskList component. Tasks persisted to Honcho as JSON preference on every mutation. Tasks loaded from Honcho on identify (session reconnect). --- backend/main.py | 27 ++++++++++++++++++++++ frontend/src/App.tsx | 3 ++- frontend/src/components/TaskList.tsx | 32 ++++++++++++++++++++++++--- frontend/src/hooks/useConversation.ts | 4 ++++ 4 files changed, 62 insertions(+), 4 deletions(-) diff --git a/backend/main.py b/backend/main.py index b3845a4..ef765d2 100644 --- a/backend/main.py +++ b/backend/main.py @@ -284,6 +284,12 @@ async def gemini_voice_ws(websocket: WebSocket): # Push updated task list to frontend after any mutation if fn_name in ("add_task", "remove_task", "complete_task", "clear_completed_tasks"): await send_tasks_to_frontend() + # Persist to Honcho + try: + if kira_memory.enabled: + kira_memory.set_user_preference(user_id, "tasks", json.dumps(tasks)) + except Exception: + pass # Send tool response back to Gemini if tool_results: @@ -332,6 +338,14 @@ async def gemini_voice_ws(websocket: WebSocket): except Exception as e: logger.warning(f"Honcho error during identify: {e}") prefs = {} + # Load tasks from Honcho + try: + saved_tasks = prefs.get("tasks", "") + if saved_tasks: + tasks.clear() + tasks.extend(json.loads(saved_tasks)) + except Exception: + pass await websocket.send_json({ "type": "identified", "user_id": user_id, @@ -392,6 +406,19 @@ async def gemini_voice_ws(websocket: WebSocket): if msg_type == "ping": await websocket.send_json({"type": "pong"}) + if msg_type == "add_task": + text = msg.get("text", "").strip() + if text: + result = execute_tool("add_task", {"text": text}, tasks) + await send_tasks_to_frontend() + # Persist to Honcho + try: + if kira_memory.enabled: + kira_memory.set_user_preference(user_id, "tasks", json.dumps(tasks)) + except Exception: + pass + continue + except WebSocketDisconnect: pass except Exception as e: diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx index bd2f797..a59ee7d 100644 --- a/frontend/src/App.tsx +++ b/frontend/src/App.tsx @@ -29,6 +29,7 @@ export default function App() { startRecording, stopRecording, tasks, + addTask, } = useConversation(); const [currentSceneId, setCurrentSceneId] = useState('cozy-room'); @@ -144,7 +145,7 @@ export default function App() {
- +
diff --git a/frontend/src/components/TaskList.tsx b/frontend/src/components/TaskList.tsx index fee6cf0..d1e6a0f 100644 --- a/frontend/src/components/TaskList.tsx +++ b/frontend/src/components/TaskList.tsx @@ -1,13 +1,23 @@ +import { useState } from 'react'; import { Task } from '../hooks/useConversation'; interface Props { tasks: Task[]; + addTask: (text: string) => void; } -export default function TaskList({ tasks }: Props) { +export default function TaskList({ tasks, addTask }: Props) { + const [input, setInput] = useState(''); const pending = tasks.filter((t) => !t.completed); const done = tasks.filter((t) => t.completed); + const handleSubmit = (e: React.FormEvent) => { + e.preventDefault(); + if (!input.trim()) return; + addTask(input.trim()); + setInput(''); + }; + return (

@@ -19,9 +29,25 @@ export default function TaskList({ tasks }: Props) { )}

+
+ setInput(e.target.value)} + placeholder="add a task..." + className="flex-1 bg-white/30 border border-kira-pink/20 rounded-lg px-2 py-1 text-xs text-kira-plum placeholder:text-kira-plum/30 focus:outline-none focus:border-kira-pink/50" + /> + +
+ {tasks.length === 0 && ( -
- tell Kira what you need to do! +
+ tell Kira or type a task above!
)} diff --git a/frontend/src/hooks/useConversation.ts b/frontend/src/hooks/useConversation.ts index ac68efe..2e736a3 100644 --- a/frontend/src/hooks/useConversation.ts +++ b/frontend/src/hooks/useConversation.ts @@ -331,5 +331,9 @@ export function useConversation() { startRecording, stopRecording, tasks, + addTask: (text: string) => { + if (!text.trim() || !wsRef.current || wsRef.current.readyState !== WebSocket.OPEN) return; + wsRef.current.send(JSON.stringify({ type: 'add_task', text: text.trim() })); + }, }; }