mirror of
https://github.com/leonvanzyl/autocoder.git
synced 2026-03-17 02:43:09 +00:00
feat: add structured questions (AskUserQuestion) to assistant chat
Add interactive multiple-choice question support to the project assistant, allowing it to present clickable options when clarification is needed. Backend changes: - Add ask_user MCP tool to feature_mcp.py with input validation - Add mcp__features__ask_user to assistant allowed tools list - Intercept ask_user tool calls in _query_claude() to yield question messages - Add answer WebSocket message handler in assistant_chat router - Document ask_user tool in assistant system prompt Frontend changes: - Add AssistantChatQuestionMessage type and update server message union - Add currentQuestions state and sendAnswer() to useAssistantChat hook - Handle question WebSocket messages by attaching to last assistant message - Render QuestionOptions component between messages and input area - Disable text input while structured questions are active Flow: Claude calls ask_user → backend intercepts → WebSocket question message → frontend renders QuestionOptions → user clicks options → answer sent back → Claude receives formatted answer and continues conversation. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -207,12 +207,14 @@ async def assistant_chat_websocket(websocket: WebSocket, project_name: str):
|
||||
Client -> Server:
|
||||
- {"type": "start", "conversation_id": int | null} - Start/resume session
|
||||
- {"type": "message", "content": "..."} - Send user message
|
||||
- {"type": "answer", "answers": {...}} - Answer to structured questions
|
||||
- {"type": "ping"} - Keep-alive ping
|
||||
|
||||
Server -> Client:
|
||||
- {"type": "conversation_created", "conversation_id": int} - New conversation created
|
||||
- {"type": "text", "content": "..."} - Text chunk from Claude
|
||||
- {"type": "tool_call", "tool": "...", "input": {...}} - Tool being called
|
||||
- {"type": "question", "questions": [...]} - Structured questions for user
|
||||
- {"type": "response_done"} - Response complete
|
||||
- {"type": "error", "content": "..."} - Error message
|
||||
- {"type": "pong"} - Keep-alive pong
|
||||
@@ -303,6 +305,34 @@ async def assistant_chat_websocket(websocket: WebSocket, project_name: str):
|
||||
async for chunk in session.send_message(user_content):
|
||||
await websocket.send_json(chunk)
|
||||
|
||||
elif msg_type == "answer":
|
||||
# User answered a structured question
|
||||
if not session:
|
||||
session = get_session(project_name)
|
||||
if not session:
|
||||
await websocket.send_json({
|
||||
"type": "error",
|
||||
"content": "No active session. Send 'start' first."
|
||||
})
|
||||
continue
|
||||
|
||||
# Format the answers as a natural response
|
||||
answers = message.get("answers", {})
|
||||
if isinstance(answers, dict):
|
||||
response_parts = []
|
||||
for question_idx, answer_value in answers.items():
|
||||
if isinstance(answer_value, list):
|
||||
response_parts.append(", ".join(answer_value))
|
||||
else:
|
||||
response_parts.append(str(answer_value))
|
||||
user_response = "; ".join(response_parts) if response_parts else "OK"
|
||||
else:
|
||||
user_response = str(answers)
|
||||
|
||||
# Stream Claude's response
|
||||
async for chunk in session.send_message(user_response):
|
||||
await websocket.send_json(chunk)
|
||||
|
||||
else:
|
||||
await websocket.send_json({
|
||||
"type": "error",
|
||||
|
||||
Reference in New Issue
Block a user